From cd4e31ab878ceeca660badf1ac5ecc5dc88dda50 Mon Sep 17 00:00:00 2001 From: Joyeleke <121850312+Joyeleke@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:36:43 -0400 Subject: [PATCH 001/178] =?UTF-8?q?feat:=20add=20event=20recording=20in=20?= =?UTF-8?q?the=20admin=20dashboard=20=E2=9C=8C=F0=9F=8F=BE=20(#123)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_dashboard.events.$id.add-recording.tsx | 127 ++++++++++++++++++ .../app/routes/_dashboard.events.tsx | 5 + apps/admin-dashboard/app/shared/constants.ts | 1 + packages/core/src/admin-dashboard.server.ts | 1 + packages/core/src/admin-dashboard.ui.ts | 1 + .../core/src/modules/event/event.types.ts | 13 ++ .../use-cases/add-event-recording-link.ts | 13 ++ .../20240408035040_upload_event_link.ts | 12 ++ packages/types/src/domain/event.ts | 1 + 9 files changed, 174 insertions(+) create mode 100644 apps/admin-dashboard/app/routes/_dashboard.events.$id.add-recording.tsx create mode 100644 packages/core/src/modules/event/event.types.ts create mode 100644 packages/core/src/modules/event/use-cases/add-event-recording-link.ts create mode 100644 packages/db/src/migrations/20240408035040_upload_event_link.ts diff --git a/apps/admin-dashboard/app/routes/_dashboard.events.$id.add-recording.tsx b/apps/admin-dashboard/app/routes/_dashboard.events.$id.add-recording.tsx new file mode 100644 index 00000000..fe0dd7ea --- /dev/null +++ b/apps/admin-dashboard/app/routes/_dashboard.events.$id.add-recording.tsx @@ -0,0 +1,127 @@ +import { + type ActionFunctionArgs, + json, + type LoaderFunctionArgs, + redirect, +} from '@remix-run/node'; +import { + Form as RemixForm, + useActionData, + useLoaderData, + useNavigate, +} from '@remix-run/react'; + +import { + Button, + Form, + getActionErrors, + Input, + Modal, + validateForm, +} from '@oyster/ui'; + +import { addEventRecordingLink, getEvent } from '@/admin-dashboard.server'; +import { Route } from '../shared/constants'; +import { AddEventRecordingLinkInput } from '../shared/core.ui'; +import { + commitSession, + ensureUserAuthenticated, + toast, +} from '../shared/session.server'; + +export async function loader({ params, request }: LoaderFunctionArgs) { + await ensureUserAuthenticated(request); + + const event = await getEvent(params.id as string, ['events.recordingLink']); + + if (!event) { + throw new Response(null, { status: 404 }); + } + + return json({ + event, + }); +} + +export async function action({ params, request }: ActionFunctionArgs) { + const session = await ensureUserAuthenticated(request); + + const form = await request.formData(); + + const { data, errors } = validateForm( + AddEventRecordingLinkInput, + Object.fromEntries(form) + ); + + if (!data) { + return json({ + error: 'Something went wrong, please try again to upload event link.', + errors, + }); + } + + await addEventRecordingLink(params.id as string, data); + + toast(session, { + message: 'Link uploaded successfully.', + type: 'success', + }); + + return redirect(Route.EVENTS, { + headers: { + 'Set-Cookie': await commitSession(session), + }, + }); +} + +export default function AddEventRecordingModal() { + const navigate = useNavigate(); + + function onClose() { + navigate(Route.EVENTS); + } + + return ( + + + Add Recording + + + + + + ); +} + +const keys = AddEventRecordingLinkInput.keyof().enum; + +function AddEventRecordingForm() { + const { event } = useLoaderData(); + const { error, errors } = getActionErrors(useActionData()); + + return ( + + + + + + {error} + + + + + + ); +} diff --git a/apps/admin-dashboard/app/routes/_dashboard.events.tsx b/apps/admin-dashboard/app/routes/_dashboard.events.tsx index 1fa06625..1fb49289 100644 --- a/apps/admin-dashboard/app/routes/_dashboard.events.tsx +++ b/apps/admin-dashboard/app/routes/_dashboard.events.tsx @@ -216,6 +216,11 @@ function EventDropdown({ id }: EventInView) { Import Attendees + + + Add Recording + + )} diff --git a/apps/admin-dashboard/app/shared/constants.ts b/apps/admin-dashboard/app/shared/constants.ts index 57e2cb8c..9f0fc9e5 100644 --- a/apps/admin-dashboard/app/shared/constants.ts +++ b/apps/admin-dashboard/app/shared/constants.ts @@ -21,6 +21,7 @@ export const Route = { // Events + ADD_EVENT_RECORDING: '/events/:id/add-recording', CREATE_EVENT: '/events/create', EVENTS: '/events', IMPORT_EVENT_ATTENDEES: '/events/:id/import', diff --git a/packages/core/src/admin-dashboard.server.ts b/packages/core/src/admin-dashboard.server.ts index 34dd3ff4..169d8a44 100644 --- a/packages/core/src/admin-dashboard.server.ts +++ b/packages/core/src/admin-dashboard.server.ts @@ -13,6 +13,7 @@ export { verifyOneTimeCode } from './modules/authentication/use-cases/verify-one export { createSchool } from './modules/education/use-cases/create-school'; export { getEvent } from './modules/event/queries/get-event'; export { listEvents } from './modules/event/queries/list-events'; +export { addEventRecordingLink } from './modules/event/use-cases/add-event-recording-link'; export { createEvent } from './modules/event/use-cases/create-event'; export { archiveActivity } from './modules/gamification/use-cases/archive-activity'; export { editActivity } from './modules/gamification/use-cases/edit-activity'; diff --git a/packages/core/src/admin-dashboard.ui.ts b/packages/core/src/admin-dashboard.ui.ts index ae48f180..2dea4869 100644 --- a/packages/core/src/admin-dashboard.ui.ts +++ b/packages/core/src/admin-dashboard.ui.ts @@ -10,6 +10,7 @@ export { CreateSchoolInput, EducationLevel, } from './modules/education/education.types'; +export { AddEventRecordingLinkInput } from './modules/event/event.types'; export { AddIcebreakerPromptInput } from './modules/icebreaker/icebreaker.types'; export { OnboardingSession } from './modules/onboarding-session/onboarding-session.types'; export { CreateSurveyInput } from './modules/survey/survey.types'; diff --git a/packages/core/src/modules/event/event.types.ts b/packages/core/src/modules/event/event.types.ts new file mode 100644 index 00000000..1b7ac558 --- /dev/null +++ b/packages/core/src/modules/event/event.types.ts @@ -0,0 +1,13 @@ +import { type z } from 'zod'; + +import { Event } from '@oyster/types'; + +// Use Cases + +export const AddEventRecordingLinkInput = Event.pick({ + recordingLink: true, +}); + +export type AddEventRecordingLinkInput = z.infer< + typeof AddEventRecordingLinkInput +>; diff --git a/packages/core/src/modules/event/use-cases/add-event-recording-link.ts b/packages/core/src/modules/event/use-cases/add-event-recording-link.ts new file mode 100644 index 00000000..9f5fa377 --- /dev/null +++ b/packages/core/src/modules/event/use-cases/add-event-recording-link.ts @@ -0,0 +1,13 @@ +import { db } from '@/infrastructure/database'; +import { type AddEventRecordingLinkInput } from '@/modules/event/event.types'; + +export async function addEventRecordingLink( + id: string, + { recordingLink }: AddEventRecordingLinkInput +) { + await db + .updateTable('events') + .set({ recordingLink }) + .where('id', '=', id) + .executeTakeFirst(); +} diff --git a/packages/db/src/migrations/20240408035040_upload_event_link.ts b/packages/db/src/migrations/20240408035040_upload_event_link.ts new file mode 100644 index 00000000..d4a6a142 --- /dev/null +++ b/packages/db/src/migrations/20240408035040_upload_event_link.ts @@ -0,0 +1,12 @@ +import { type Kysely } from 'kysely'; + +export async function up(db: Kysely) { + await db.schema + .alterTable('events') + .addColumn('recording_link', 'text') + .execute(); +} + +export async function down(db: Kysely) { + await db.schema.alterTable('events').dropColumn('recording_link').execute(); +} diff --git a/packages/types/src/domain/event.ts b/packages/types/src/domain/event.ts index 90ac0656..42fff261 100644 --- a/packages/types/src/domain/event.ts +++ b/packages/types/src/domain/event.ts @@ -21,6 +21,7 @@ export const Event = z.object({ externalLink: z.string().url().nullable(), id: Entity.shape.id, name: z.string().trim().min(1), + recordingLink: z.string().url().nullish(), startTime: z.coerce.date(), type: z.nativeEnum(EventType), }); From 6e2405f3a2b5742693416e27cb863b830504aa94 Mon Sep 17 00:00:00 2001 From: Rami Abdou <38056800+ramiAbdou@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:14:29 -0700 Subject: [PATCH 002/178] =?UTF-8?q?feat:=20implement=20census=20form=20(2/?= =?UTF-8?q?x)=20=F0=9F=93=9D=20(#131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/routes/_profile.census.tsx | 510 ++++++++++-------- .../use-cases/login-with-oauth.ts | 4 +- .../event/use-cases/sync-airmeet-event.ts | 4 +- .../src/modules/location/location.shared.ts | 21 +- .../queries/get-autocompleted-cities.ts | 8 +- .../location/queries/get-city-details.ts | 9 +- .../use-cases/sync-mailchimp-campaign.ts | 6 +- ...ber-by-email.ts => get-member-by-email.ts} | 2 +- .../src/modules/member/queries/list-emails.ts | 11 +- .../slack/events/slack-workspace-joined.ts | 4 +- .../use-cases/import-survey-responses.ts | 4 +- packages/ui/src/components/radio.tsx | 5 +- .../ui/src/hooks/use-revalidate-on-focus.ts | 25 + packages/ui/src/index.ts | 1 + 14 files changed, 376 insertions(+), 238 deletions(-) rename packages/core/src/modules/member/queries/{find-member-by-email.ts => get-member-by-email.ts} (84%) create mode 100644 packages/ui/src/hooks/use-revalidate-on-focus.ts diff --git a/apps/member-profile/app/routes/_profile.census.tsx b/apps/member-profile/app/routes/_profile.census.tsx index a1a5bb51..574f6171 100644 --- a/apps/member-profile/app/routes/_profile.census.tsx +++ b/apps/member-profile/app/routes/_profile.census.tsx @@ -9,12 +9,13 @@ import { useLoaderData, useSubmit, } from '@remix-run/react'; -import { useState } from 'react'; +import { type PropsWithChildren, useState } from 'react'; import { z } from 'zod'; import { db } from '@oyster/db'; import { Checkbox, + Divider, type FieldProps, Form, Input, @@ -22,6 +23,7 @@ import { Select, Text, Textarea, + useRevalidateOnFocus, validateForm, } from '@oyster/ui'; import { iife } from '@oyster/utils'; @@ -38,17 +40,13 @@ export async function loader({ request }: LoaderFunctionArgs) { const [emails] = await Promise.all([listEmails(memberId)]); - const primaryEmail = iife(() => { - const result = emails.find((email) => { - return email.primary; - }); - - return result ? result.email! : undefined; - }); + const primaryEmail = emails.find((email) => { + return !!email.primary; + })!; return json({ emails, - primaryEmail, + primaryEmail: primaryEmail.email, }); } @@ -75,6 +73,8 @@ export async function action({ request }: ActionFunctionArgs) { } export default function CensusPage() { + useRevalidateOnFocus(); + return (
ColorStack Census '24 @@ -93,6 +93,7 @@ function CensusForm() { const submit = useSubmit(); + const [hasGraduated, setHasGraduated] = useState(null); const [hasInternship, setHasInternship] = useState(false); return ( @@ -101,243 +102,322 @@ function CensusForm() { method="post" onBlur={(e) => submit(e.currentTarget)} > - { - return ( + + - If your preferred email is not listed here, please add it{' '} + If you'd like to change your primary email, please add that email{' '} here {' '} first. - ); - })} - error="" - label="Email" - labelFor="email" - required - > - - + } + error="" + label="Email" + labelFor="email" + required + > + + + - - - + + + + setHasGraduated(e.currentTarget.value === '1')} + required + value="1" + /> + setHasGraduated(e.currentTarget.value === '1')} + required + value="0" + /> + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + )} + + + {hasGraduated === false && ( + + - - - - - - setHasInternship(e.currentTarget.value === '1')} + > + + + setHasInternship(e.currentTarget.value === '1') + } + required + value="1" + /> + + setHasInternship(e.currentTarget.value === '1') + } + required + value="0" + /> + + + + {hasInternship && ( + + + + )} + + {hasInternship && ( + + + + )} + + - setHasInternship(e.currentTarget.value === '1')} + > + + + + - - + > + + + + )} - {hasInternship && ( + - + {iife(() => { + const resources = [ + 'AlgoExpert', + 'Wiki', + 'Fam Fridays', + 'Slack', + 'Newsletter', + 'InterviewPen', + 'CompSciLib', + ]; + + return ( + + {resources.map((resource) => { + return ( + + ); + })} + + ); + })} - )} - {hasInternship && ( - +