From c0772eca8323ceecb3f5d5696019b61a2cc1974d Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 26 Oct 2024 19:45:56 -0400 Subject: [PATCH 01/24] feat: add onboarding dialog skeleton --- apps/web/src/app/(pages)/(dashboard)/page.tsx | 8 ++- .../src/app/_components/onboarding/dialog.tsx | 65 +++++++++++++++++++ .../onboarding/onboarding-form.tsx | 0 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/app/_components/onboarding/dialog.tsx create mode 100644 apps/web/src/app/_components/onboarding/onboarding-form.tsx diff --git a/apps/web/src/app/(pages)/(dashboard)/page.tsx b/apps/web/src/app/(pages)/(dashboard)/page.tsx index 45b8310..edcad00 100644 --- a/apps/web/src/app/(pages)/(dashboard)/page.tsx +++ b/apps/web/src/app/(pages)/(dashboard)/page.tsx @@ -1,10 +1,16 @@ +import { auth } from "@cooper/auth"; + +import { OnboardingDialog } from "~/app/_components/onboarding/dialog"; import SearchFilter from "~/app/_components/search/search-filter"; -export default function Home() { +export default async function Home() { + const session = await auth(); + return (
+
); diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx new file mode 100644 index 0000000..bd7957d --- /dev/null +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useState } from "react"; +import { signIn } from "next-auth/react"; + +import type { Session } from "@cooper/auth"; +import { Button } from "@cooper/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@cooper/ui/dialog"; + +import { api } from "~/trpc/react"; + +interface OnboardingDialogProps { + isOpen: boolean; + session: Session | null; +} + +export function OnboardingDialog({ isOpen, session }: OnboardingDialogProps) { + const [open, setOpen] = useState(isOpen); + + const profile = api.profile.getCurrentUser.useQuery(); + + return ( + + + + + {session ? "Create a Cooper Account" : "Login"} + + + {!session && } + {!profile.data &&

User Onboarding Flow

} +
+
+ ); +} + +const SignInWithGoogleButton = () => { + const handleGoogleSignIn = () => { + void signIn("google"); + }; + + return ( +
+ +
+ ); +}; diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx new file mode 100644 index 0000000..e69de29 From 7f2d845b1b0c3f8f4e4dc8f365f0deeb56c6001e Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 26 Oct 2024 21:23:21 -0400 Subject: [PATCH 02/24] feat: add basic onboarding flow --- .../_components/onboarding/constants/index.ts | 21 ++ .../src/app/_components/onboarding/dialog.tsx | 9 +- .../onboarding/onboarding-form.tsx | 187 ++++++++++++++++++ .../_components/themed/onboarding/form.tsx | 14 ++ .../_components/themed/onboarding/input.tsx | 10 + .../_components/themed/onboarding/select.tsx | 40 ++++ 6 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/app/_components/onboarding/constants/index.ts create mode 100644 apps/web/src/app/_components/themed/onboarding/form.tsx create mode 100644 apps/web/src/app/_components/themed/onboarding/input.tsx create mode 100644 apps/web/src/app/_components/themed/onboarding/select.tsx diff --git a/apps/web/src/app/_components/onboarding/constants/index.ts b/apps/web/src/app/_components/onboarding/constants/index.ts new file mode 100644 index 0000000..14c3b30 --- /dev/null +++ b/apps/web/src/app/_components/onboarding/constants/index.ts @@ -0,0 +1,21 @@ +export const monthOptions = [ + { value: "1", label: "January" }, + { value: "2", label: "February" }, + { value: "3", label: "March" }, + { value: "4", label: "April" }, + { value: "5", label: "May" }, + { value: "6", label: "June" }, + { value: "7", label: "July" }, + { value: "8", label: "August" }, + { value: "9", label: "September" }, + { value: "10", label: "October" }, + { value: "11", label: "November" }, + { value: "12", label: "December" }, +]; + +export const majors = [ + "Computer Science", + "Computer Science + Math", + "Computer Science + Business", + "Computer Science + Design", +]; diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index bd7957d..ca64055 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -13,6 +13,7 @@ import { } from "@cooper/ui/dialog"; import { api } from "~/trpc/react"; +import { OnboardingForm } from "./onboarding-form"; interface OnboardingDialogProps { isOpen: boolean; @@ -26,14 +27,16 @@ export function OnboardingDialog({ isOpen, session }: OnboardingDialogProps) { return ( - + - + {session ? "Create a Cooper Account" : "Login"} {!session && } - {!profile.data &&

User Onboarding Flow

} + {!profile.data && session && ( + + )}
); diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx index e69de29..993fbea 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-form.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-form.tsx @@ -0,0 +1,187 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { Button } from "@cooper/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@cooper/ui/form"; + +import { FormLabel } from "~/app/_components/themed/onboarding/form"; +import { Input } from "~/app/_components/themed/onboarding/input"; +import { api } from "~/trpc/react"; +import { Select } from "../themed/onboarding/select"; +import { majors, monthOptions } from "./constants"; + +const formSchema = z.object({ + firstName: z.string().min(1, "First name is required"), + lastName: z.string().min(1, "Last name is required"), + email: z + .string() + .min(1, { message: "Email is required" }) + .email("This is not a valid email"), + major: z.string().min(1, "Major is required"), + minor: z.string().optional(), + graduationYear: z.coerce + .number() + .min(2010, "Graduation year must be 2010 or later") + .max(2030, "Graduation year must be 2030 or earlier"), + graduationMonth: z.coerce + .number() + .min(1, "Graduation month is required") + .max(12, "Invalid month"), + cooped: z.boolean(), +}); + +export type OnboardingFormType = typeof formSchema; + +interface OnboardingFormProps { + userId: string; +} + +export function OnboardingForm({ userId }: OnboardingFormProps) { + const profile = api.profile.create.useMutation(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + firstName: "", + lastName: "", + email: "", + major: "", + minor: "", + graduationYear: undefined, + graduationMonth: 0, + cooped: false, + }, + }); + + const onSubmit = (data: z.infer) => { + profile.mutate({ userId, ...data }); + }; + + return ( +
+ +
+ ( + + First Name + + + + + + )} + /> + ( + + Last Name + + + + + + )} + /> +
+ ( + + Email + + + + + + )} + /> +
+ ( + + Major + + + + + + )} + /> +
+
+ ( + + Graduation Year + + + + + + )} + /> + ( + + Graduation Month + + + {placeholder && } + {options.map((option) => ( + + ))} + +
+ + + +
+
+ ); +}; From 1d04b460a7ceae162ccaba1aa52ffc6427b84bdc Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 26 Oct 2024 22:06:12 -0400 Subject: [PATCH 03/24] feat: change onboarding flow order sequence --- apps/web/public/logo.png | Bin 0 -> 8320 bytes .../src/app/_components/onboarding/dialog.tsx | 13 +++++++++---- .../_components/onboarding/onboarding-form.tsx | 10 ++++++---- tooling/tailwind/web.ts | 2 ++ 4 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 apps/web/public/logo.png diff --git a/apps/web/public/logo.png b/apps/web/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..04822c30b584fe6ca4f43b26c93326719c8b1d9a GIT binary patch literal 8320 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 @@ -33,10 +40,8 @@ export function OnboardingDialog({ isOpen, session }: OnboardingDialogProps) { {session ? "Create a Cooper Account" : "Login"} - {!session && } - {!profile.data && session && ( - - )} + {shouldShowSignIn && } + {shouldShowOnboarding && } ); diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx index 993fbea..fb40e53 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-form.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-form.tsx @@ -53,7 +53,7 @@ export function OnboardingForm({ userId }: OnboardingFormProps) { lastName: "", email: "", major: "", - minor: "", + minor: undefined, graduationYear: undefined, graduationMonth: 0, cooped: false, @@ -178,9 +178,11 @@ export function OnboardingForm({ userId }: OnboardingFormProps) { )} /> - +
+ +
); diff --git a/tooling/tailwind/web.ts b/tooling/tailwind/web.ts index 6602cfa..1329d98 100644 --- a/tooling/tailwind/web.ts +++ b/tooling/tailwind/web.ts @@ -22,6 +22,8 @@ export default { colors: { "cooper-gray-100": "#DDE8F0", "cooper-gray-200": "#64748B", + "cooper-gray-300": "#E6E6E6", + "cooper-gray-400": "#949494", "cooper-blue-800": "#5A9478", "cooper-blue-700": "#1D679C", "cooper-blue-600": "#436F8E", From 2afc5e04521eec426e6805f6e6e17d369b18ea11 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 26 Oct 2024 22:13:16 -0400 Subject: [PATCH 04/24] chore: format --- apps/web/src/app/_components/themed/onboarding/input.tsx | 2 +- apps/web/src/app/_components/themed/onboarding/select.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/_components/themed/onboarding/input.tsx b/apps/web/src/app/_components/themed/onboarding/input.tsx index 0299f7e..98d3b2a 100644 --- a/apps/web/src/app/_components/themed/onboarding/input.tsx +++ b/apps/web/src/app/_components/themed/onboarding/input.tsx @@ -3,7 +3,7 @@ import { Input as InputPrimitive } from "@cooper/ui/input"; export function Input(props: React.ComponentProps) { return ( ); diff --git a/apps/web/src/app/_components/themed/onboarding/select.tsx b/apps/web/src/app/_components/themed/onboarding/select.tsx index f11b456..dfab507 100644 --- a/apps/web/src/app/_components/themed/onboarding/select.tsx +++ b/apps/web/src/app/_components/themed/onboarding/select.tsx @@ -18,7 +18,7 @@ export const Select: React.FC = ({
-
+
From 55a5f3665876e701915a4d745a8809d5c315bd83 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sun, 27 Oct 2024 14:40:50 -0400 Subject: [PATCH 05/24] chore: add isloading to return null branch --- apps/web/src/app/_components/onboarding/dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index 9857919..1dd33bd 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -28,7 +28,7 @@ export function OnboardingDialog({ isOpen, session }: OnboardingDialogProps) { const shouldShowSignIn = !session; const shouldShowOnboarding = session && !profile.data; - if (!shouldShowOnboarding && !shouldShowSignIn) { + if ((!shouldShowOnboarding && !shouldShowSignIn) || profile.isLoading) { return null; } From 2472634eee56aef92b5941d2848ecd8fde72a4b6 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Thu, 21 Nov 2024 08:14:17 -0500 Subject: [PATCH 06/24] feat: order by created at prayge --- packages/api/src/router/review.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/router/review.ts b/packages/api/src/router/review.ts index 0cec664..a124242 100644 --- a/packages/api/src/router/review.ts +++ b/packages/api/src/router/review.ts @@ -27,7 +27,7 @@ export const reviewRouter = { ].filter(Boolean); return ctx.db.query.Review.findMany({ - orderBy: desc(Review.id), + orderBy: desc(Review.createdAt), where: conditions.length > 0 ? and(...conditions) : undefined, }); }), From 816626e923b6a84c93f7ae2bf5019c3c72de66f0 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Wed, 27 Nov 2024 15:37:20 -0500 Subject: [PATCH 07/24] feat: add a reusuable onboarding wrapper --- .../src/app/(pages)/(dashboard)/layout.tsx | 7 ++++++- apps/web/src/app/(pages)/(dashboard)/page.tsx | 8 +------- .../src/app/_components/onboarding/dialog.tsx | 13 +++++++++--- .../onboarding/onboarding-wrapper.tsx | 20 +++++++++++++++++++ 4 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx diff --git a/apps/web/src/app/(pages)/(dashboard)/layout.tsx b/apps/web/src/app/(pages)/(dashboard)/layout.tsx index 151f8a0..6566f90 100644 --- a/apps/web/src/app/(pages)/(dashboard)/layout.tsx +++ b/apps/web/src/app/(pages)/(dashboard)/layout.tsx @@ -1,9 +1,14 @@ import HeaderLayout from "~/app/_components/header-layout"; +import { OnboardingWrapper } from "~/app/_components/onboarding/onboarding-wrapper"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { - return {children}; + return ( + + {children} + + ); } diff --git a/apps/web/src/app/(pages)/(dashboard)/page.tsx b/apps/web/src/app/(pages)/(dashboard)/page.tsx index edcad00..45b8310 100644 --- a/apps/web/src/app/(pages)/(dashboard)/page.tsx +++ b/apps/web/src/app/(pages)/(dashboard)/page.tsx @@ -1,16 +1,10 @@ -import { auth } from "@cooper/auth"; - -import { OnboardingDialog } from "~/app/_components/onboarding/dialog"; import SearchFilter from "~/app/_components/search/search-filter"; -export default async function Home() { - const session = await auth(); - +export default function Home() { return (
-
); diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index 1dd33bd..e6235a2 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -16,11 +16,14 @@ import { api } from "~/trpc/react"; import { OnboardingForm } from "./onboarding-form"; interface OnboardingDialogProps { - isOpen: boolean; + isOpen?: boolean; session: Session | null; } -export function OnboardingDialog({ isOpen, session }: OnboardingDialogProps) { +export function OnboardingDialog({ + isOpen = true, + session, +}: OnboardingDialogProps) { const [open, setOpen] = useState(isOpen); const profile = api.profile.getCurrentUser.useQuery(); @@ -28,7 +31,11 @@ export function OnboardingDialog({ isOpen, session }: OnboardingDialogProps) { const shouldShowSignIn = !session; const shouldShowOnboarding = session && !profile.data; - if ((!shouldShowOnboarding && !shouldShowSignIn) || profile.isLoading) { + if (profile.isPending) { + return null; + } + + if (!shouldShowOnboarding && !shouldShowSignIn) { return null; } diff --git a/apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx b/apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx new file mode 100644 index 0000000..9e2a8d3 --- /dev/null +++ b/apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from "react"; + +import { auth } from "@cooper/auth"; + +import { OnboardingDialog } from "~/app/_components/onboarding/dialog"; + +interface OnboardingWrapperProps { + children: ReactNode; +} + +export async function OnboardingWrapper({ children }: OnboardingWrapperProps) { + const session = await auth(); + + return ( + <> + {children} + + + ); +} From 201a217ea6ac38c77d0680aae68af3ba5c89232b Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Thu, 28 Nov 2024 06:01:19 -0500 Subject: [PATCH 08/24] feat: add cooped field --- .../src/app/(pages)/(dashboard)/layout.tsx | 2 +- .../src/app/_components/onboarding/dialog.tsx | 3 ++ .../onboarding/onboarding-form.tsx | 45 ++++++++++++++++++- .../onboarding/onboarding-wrapper.tsx | 4 +- .../post-onboarding/browse-around-prompt.tsx | 3 ++ .../post-onboarding/submit-review-prompt.tsx | 3 ++ 6 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/app/_components/onboarding/post-onboarding/browse-around-prompt.tsx create mode 100644 apps/web/src/app/_components/onboarding/post-onboarding/submit-review-prompt.tsx diff --git a/apps/web/src/app/(pages)/(dashboard)/layout.tsx b/apps/web/src/app/(pages)/(dashboard)/layout.tsx index 5bbd20e..4a4dc3d 100644 --- a/apps/web/src/app/(pages)/(dashboard)/layout.tsx +++ b/apps/web/src/app/(pages)/(dashboard)/layout.tsx @@ -1,7 +1,7 @@ import { Toaster } from "@cooper/ui/toaster"; import HeaderLayout from "~/app/_components/header-layout"; -import { OnboardingWrapper } from "~/app/_components/onboarding/onboarding-wrapper"; +import OnboardingWrapper from "~/app/_components/onboarding/onboarding-wrapper"; export default function RootLayout({ children, diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index e6235a2..2211ae4 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -47,6 +47,9 @@ export function OnboardingDialog({ {session ? "Create a Cooper Account" : "Login"} +

+ * Required +

{shouldShowSignIn && } {shouldShowOnboarding && } diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx index fb40e53..7dea190 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-form.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-form.tsx @@ -10,6 +10,7 @@ import { FormItem, FormMessage, } from "@cooper/ui/form"; +import { RadioGroup, RadioGroupItem } from "@cooper/ui/radio-group"; import { FormLabel } from "~/app/_components/themed/onboarding/form"; import { Input } from "~/app/_components/themed/onboarding/input"; @@ -34,7 +35,11 @@ const formSchema = z.object({ .number() .min(1, "Graduation month is required") .max(12, "Invalid month"), - cooped: z.boolean(), + cooped: z + .string() + .toLowerCase() + .transform((x) => x === "true") + .pipe(z.boolean()), }); export type OnboardingFormType = typeof formSchema; @@ -56,7 +61,7 @@ export function OnboardingForm({ userId }: OnboardingFormProps) { minor: undefined, graduationYear: undefined, graduationMonth: 0, - cooped: false, + cooped: undefined, }, }); @@ -178,6 +183,42 @@ export function OnboardingForm({ userId }: OnboardingFormProps) { )} />
+ ( + + Have you completed a co-op before? + + + + + + + Yes + + + + + + No + + + + + + )} + />
+
+ ); +} From 29d07c510a337b48937facd47a7a7cb04f081d4f Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Thu, 28 Nov 2024 15:22:27 -0500 Subject: [PATCH 10/24] feat: add jank af (pls destroy this code when reviewing) post onboarding dialog --- .../src/app/_components/onboarding/dialog.tsx | 20 +- .../onboarding/onboarding-form.tsx | 333 +++++++++--------- .../_components/themed/onboarding/form.tsx | 11 +- 3 files changed, 193 insertions(+), 171 deletions(-) diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index 92bc38e..198f312 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -5,12 +5,7 @@ import { signIn } from "next-auth/react"; import type { Session } from "@cooper/auth"; import { Button } from "@cooper/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@cooper/ui/dialog"; +import { Dialog, DialogContent } from "@cooper/ui/dialog"; import { OnboardingForm } from "~/app/_components/onboarding/onboarding-form"; import { api } from "~/trpc/react"; @@ -31,6 +26,10 @@ export function OnboardingDialog({ const shouldShowSignIn = !session; const shouldShowOnboarding = session && !profile.data; + const closeDialog = () => { + setOpen(false); + }; + if (profile.isPending) { return null; } @@ -42,13 +41,10 @@ export function OnboardingDialog({ return ( - - - {session ? "Create a Cooper Account" : "Login"} - - {shouldShowSignIn && } - {shouldShowOnboarding && } + {shouldShowOnboarding && ( + + )} ); diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx index ca29977..56f34d6 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-form.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-form.tsx @@ -1,22 +1,23 @@ +import { useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { Button } from "@cooper/ui/button"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@cooper/ui/form"; +import { DialogTitle } from "@cooper/ui/dialog"; +import { Form, FormControl, FormField, FormItem } from "@cooper/ui/form"; import { RadioGroup, RadioGroupItem } from "@cooper/ui/radio-group"; -import { FormLabel } from "~/app/_components/themed/onboarding/form"; +import { + FormLabel, + FormMessage, +} from "~/app/_components/themed/onboarding/form"; import { Input } from "~/app/_components/themed/onboarding/input"; import { api } from "~/trpc/react"; import { Select } from "../themed/onboarding/select"; import { majors, monthOptions } from "./constants"; +import { BrowseAroundPrompt } from "./post-onboarding/browse-around-prompt"; +import { CoopPrompt } from "./post-onboarding/coop-prompt"; const formSchema = z.object({ firstName: z.string().min(1, "First name is required"), @@ -35,23 +36,23 @@ const formSchema = z.object({ .number() .min(1, "Graduation month is required") .max(12, "Invalid month"), - cooped: z - .string() - .toLowerCase() - .transform((x) => x === "true") - .pipe(z.boolean()), + cooped: z.boolean({ + required_error: "Please select whether you've completed a co-op before", + }), }); -export type OnboardingFormType = typeof formSchema; +export type OnboardingFormType = z.infer; interface OnboardingFormProps { userId: string; + closeDialog: () => void; } -export function OnboardingForm({ userId }: OnboardingFormProps) { +export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { + const [cooped, setCooped] = useState(undefined); const profile = api.profile.create.useMutation(); - const form = useForm>({ + const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { firstName: "", @@ -65,169 +66,185 @@ export function OnboardingForm({ userId }: OnboardingFormProps) { }, }); - const onSubmit = (data: z.infer) => { + const onSubmit = (data: OnboardingFormType) => { profile.mutate({ userId, ...data }); }; + if (profile.isSuccess) { + const firstName = form.getValues("firstName"); + + return cooped ? ( + + ) : ( + + ); + } + return ( - -

- * Required -

- -
- ( - - First Name - - - - - - )} - /> - ( - - Last Name - - - - - - )} - /> -
- ( - - Email - - - - - - )} - /> -
- ( - - Major - - - - - - )} - /> -
-
+ <> + + Create a Cooper Account + + +

+ * Required +

+ +
+ ( + + First Name + + + + + + )} + /> + ( + + Last Name + + + + + + )} + /> +
( - - Graduation Year + + Email - + )} /> +
+ ( + + Major + + + + + + )} + /> +
+
+ ( + + Graduation Year + + + + + + )} + /> + ( + + Graduation Month + + + { + // FIXME: There has to be a cleaner way of doing this but I have other things to fix atm. + const hasCooped = value === "true"; + setCooped(hasCooped); + field.onChange(hasCooped); + }} + value={cooped?.toString()} + className="flex flex-col space-y-3" + > + + + + + Yes + + + + + + No + + )} /> -
- ( - - Have you completed a co-op before? - - - - - - - Yes - - - - - - No - - - - - - )} - /> -
- -
- - +
+ +
+ + + ); } diff --git a/apps/web/src/app/_components/themed/onboarding/form.tsx b/apps/web/src/app/_components/themed/onboarding/form.tsx index d60982b..d2e9f50 100644 --- a/apps/web/src/app/_components/themed/onboarding/form.tsx +++ b/apps/web/src/app/_components/themed/onboarding/form.tsx @@ -1,4 +1,7 @@ -import { FormLabel as FormLabelPrimitive } from "@cooper/ui/form"; +import { + FormLabel as FormLabelPrimitive, + FormMessage as FormMessagePrimitive, +} from "@cooper/ui/form"; export function FormLabel({ children, @@ -12,3 +15,9 @@ export function FormLabel({ ); } + +export function FormMessage( + props: React.ComponentProps, +) { + return ; +} From 7eb746f15e1e96b5b58fa0b798be46954a6c10c7 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Thu, 28 Nov 2024 15:27:42 -0500 Subject: [PATCH 11/24] chore: remove unused logo --- apps/web/public/logo-tight.png | Bin 3000 -> 0 bytes apps/web/public/logo.png | Bin 8320 -> 3000 bytes .../post-onboarding/welcome-dialog.tsx | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 apps/web/public/logo-tight.png diff --git a/apps/web/public/logo-tight.png b/apps/web/public/logo-tight.png deleted file mode 100644 index 9d07e1d074c0a5df4d25f97f31cb089e4bb30ae4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3000 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 - Cooper Logo + Cooper Logo

{heading}

From 5504fe16bb675618d83bc7fc3169c02ab4070a4b Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Thu, 28 Nov 2024 15:34:33 -0500 Subject: [PATCH 12/24] docs: add basic jsdocs --- apps/web/src/app/_components/onboarding/dialog.tsx | 7 +++++++ .../web/src/app/_components/onboarding/onboarding-form.tsx | 6 ++++++ .../src/app/_components/onboarding/onboarding-wrapper.tsx | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index 198f312..f482fb1 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -15,6 +15,13 @@ interface OnboardingDialogProps { session: Session | null; } +/** + * OnboardingDialog component that handles user onboarding. + * Implementation note: Use OnboardingWrapper to wrap the component and initiate the dialog. + * @param isOpen - Whether the dialog is open + * @param session - The current user session + * @returns The OnboardingDialog component or null + */ export function OnboardingDialog({ isOpen = true, session, diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx index 56f34d6..ee367c1 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-form.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-form.tsx @@ -48,6 +48,12 @@ interface OnboardingFormProps { closeDialog: () => void; } +/** + * OnboardingForm component that handles user onboarding. + * @param userId - The user ID + * @param closeDialog - The function to close the dialog + * @returns The OnboardingForm component + */ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { const [cooped, setCooped] = useState(undefined); const profile = api.profile.create.useMutation(); diff --git a/apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx b/apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx index 7804947..fe31b20 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-wrapper.tsx @@ -8,6 +8,11 @@ interface OnboardingWrapperProps { children: ReactNode; } +/** + * OnboardingWrapper component that wraps the app and initiates the onboarding dialog. + * @param children - The children components + * @returns The OnboardingWrapper component + */ export default async function OnboardingWrapper({ children, }: OnboardingWrapperProps) { From 24ee7127cdbdf737bc7e66e2a63422ac228eee34 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Thu, 28 Nov 2024 15:49:22 -0500 Subject: [PATCH 13/24] refactor: rename variables and add dialog title --- .../src/app/_components/onboarding/dialog.tsx | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index f482fb1..9fb08a4 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -5,7 +5,7 @@ import { signIn } from "next-auth/react"; import type { Session } from "@cooper/auth"; import { Button } from "@cooper/ui/button"; -import { Dialog, DialogContent } from "@cooper/ui/dialog"; +import { Dialog, DialogContent, DialogTitle } from "@cooper/ui/dialog"; import { OnboardingForm } from "~/app/_components/onboarding/onboarding-form"; import { api } from "~/trpc/react"; @@ -48,7 +48,7 @@ export function OnboardingDialog({ return ( - {shouldShowSignIn && } + {shouldShowSignIn && } {shouldShowOnboarding && ( )} @@ -57,27 +57,32 @@ export function OnboardingDialog({ ); } -const SignInWithGoogleButton = () => { +const SignInWithGooglePrompt = () => { const handleGoogleSignIn = () => { void signIn("google"); }; return ( -
- -
+ + Sign in with Google + +
+ ); }; From 420487e3da91768973db86865d50f3ed493be892 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Fri, 29 Nov 2024 14:01:31 -0500 Subject: [PATCH 14/24] chore: also do onboarding flow on the homepage --- apps/web/src/app/layout.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 28aa0d7..fe8d0ad 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -7,6 +7,7 @@ import { TRPCReactProvider } from "~/trpc/react"; import "~/app/styles/globals.css"; +import OnboardingWrapper from "~/app/_components/onboarding/onboarding-wrapper"; import { env } from "~/env"; export const metadata: Metadata = { @@ -37,7 +38,9 @@ export default function RootLayout(props: { children: React.ReactNode }) { "min-h-screen bg-background font-sans text-foreground antialiased", )} > - {props.children} + + {props.children} + ); From fdfb27b261f1331af27bc09369c6df9836ef0558 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Fri, 29 Nov 2024 14:04:20 -0500 Subject: [PATCH 15/24] chore: remove that last change don't need it cause it stacks? --- apps/web/src/app/layout.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index fe8d0ad..28aa0d7 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -7,7 +7,6 @@ import { TRPCReactProvider } from "~/trpc/react"; import "~/app/styles/globals.css"; -import OnboardingWrapper from "~/app/_components/onboarding/onboarding-wrapper"; import { env } from "~/env"; export const metadata: Metadata = { @@ -38,9 +37,7 @@ export default function RootLayout(props: { children: React.ReactNode }) { "min-h-screen bg-background font-sans text-foreground antialiased", )} > - - {props.children} - + {props.children} ); From 2a163ddec6bb2eb2faab0d4917adb657a64b1ce2 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Fri, 29 Nov 2024 19:19:18 -0500 Subject: [PATCH 16/24] feat: graduation year is current + 6 --- apps/web/src/app/_components/onboarding/onboarding-form.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx index ee367c1..ff42b80 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-form.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-form.tsx @@ -19,6 +19,8 @@ import { majors, monthOptions } from "./constants"; import { BrowseAroundPrompt } from "./post-onboarding/browse-around-prompt"; import { CoopPrompt } from "./post-onboarding/coop-prompt"; +const currentYear = new Date().getFullYear(); + const formSchema = z.object({ firstName: z.string().min(1, "First name is required"), lastName: z.string().min(1, "Last name is required"), @@ -31,7 +33,7 @@ const formSchema = z.object({ graduationYear: z.coerce .number() .min(2010, "Graduation year must be 2010 or later") - .max(2030, "Graduation year must be 2030 or earlier"), + .max(currentYear + 6, "Graduation year must be within the next 5 years"), graduationMonth: z.coerce .number() .min(1, "Graduation month is required") From f07e247a45df8cf8ad84c6280d0172ea4fe4e4c9 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Fri, 29 Nov 2024 19:24:17 -0500 Subject: [PATCH 17/24] feat: switch out png for svg --- apps/web/public/logo.png | Bin 3000 -> 0 bytes apps/web/public/logo.svg | 28 ++++++++++++++++++ .../post-onboarding/welcome-dialog.tsx | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) delete mode 100644 apps/web/public/logo.png create mode 100644 apps/web/public/logo.svg diff --git a/apps/web/public/logo.png b/apps/web/public/logo.png deleted file mode 100644 index 9d07e1d074c0a5df4d25f97f31cb089e4bb30ae4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3000 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/app/_components/onboarding/post-onboarding/welcome-dialog.tsx b/apps/web/src/app/_components/onboarding/post-onboarding/welcome-dialog.tsx index 3032055..e2c2789 100644 --- a/apps/web/src/app/_components/onboarding/post-onboarding/welcome-dialog.tsx +++ b/apps/web/src/app/_components/onboarding/post-onboarding/welcome-dialog.tsx @@ -17,7 +17,7 @@ export function WelcomeDialog({ }: WelcomeDialogProps) { return (
- Cooper Logo + Cooper Logo

{heading}

From c7074dcc9ec96b773c355d6932995627c9b5743f Mon Sep 17 00:00:00 2001 From: banushi-a Date: Fri, 31 Jan 2025 19:25:44 -0500 Subject: [PATCH 18/24] Reworking Onboarding --- .../src/app/_components/onboarding/dialog.tsx | 48 ++----------------- .../onboarding/onboarding-form.tsx | 24 ++++++---- packages/api/src/root.ts | 2 + packages/api/src/router/index.ts | 10 +++- packages/api/src/router/user.ts | 17 +++++++ 5 files changed, 46 insertions(+), 55 deletions(-) create mode 100644 packages/api/src/router/user.ts diff --git a/apps/web/src/app/_components/onboarding/dialog.tsx b/apps/web/src/app/_components/onboarding/dialog.tsx index 9fb08a4..5e93b00 100644 --- a/apps/web/src/app/_components/onboarding/dialog.tsx +++ b/apps/web/src/app/_components/onboarding/dialog.tsx @@ -1,11 +1,9 @@ "use client"; import { useState } from "react"; -import { signIn } from "next-auth/react"; import type { Session } from "@cooper/auth"; -import { Button } from "@cooper/ui/button"; -import { Dialog, DialogContent, DialogTitle } from "@cooper/ui/dialog"; +import { Dialog, DialogContent } from "@cooper/ui/dialog"; import { OnboardingForm } from "~/app/_components/onboarding/onboarding-form"; import { api } from "~/trpc/react"; @@ -30,59 +28,21 @@ export function OnboardingDialog({ const profile = api.profile.getCurrentUser.useQuery(); - const shouldShowSignIn = !session; const shouldShowOnboarding = session && !profile.data; const closeDialog = () => { setOpen(false); }; - if (profile.isPending) { - return null; - } - - if (!shouldShowOnboarding && !shouldShowSignIn) { + if (profile.isPending || !shouldShowOnboarding) { return null; } return ( - - {shouldShowSignIn && } - {shouldShowOnboarding && ( - - )} + + ); } - -const SignInWithGooglePrompt = () => { - const handleGoogleSignIn = () => { - void signIn("google"); - }; - - return ( - <> - - Login - -
- -
- - ); -}; diff --git a/apps/web/src/app/_components/onboarding/onboarding-form.tsx b/apps/web/src/app/_components/onboarding/onboarding-form.tsx index ff42b80..0c948f5 100644 --- a/apps/web/src/app/_components/onboarding/onboarding-form.tsx +++ b/apps/web/src/app/_components/onboarding/onboarding-form.tsx @@ -60,6 +60,8 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { const [cooped, setCooped] = useState(undefined); const profile = api.profile.create.useMutation(); + const user = api.user.getById.useQuery({ id: userId }); + const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -90,9 +92,10 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { return ( <> - + Create a Cooper Account +

* Required @@ -101,12 +104,12 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col space-y-6" > -

+
( - + First Name @@ -119,7 +122,7 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { control={form.control} name="lastName" render={({ field }) => ( - + Last Name @@ -133,7 +136,7 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { control={form.control} name="email" render={({ field }) => ( - + Email @@ -142,12 +145,12 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { )} /> -
+
( - + Major @@ -198,11 +202,11 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { Graduation Month - @@ -199,14 +197,15 @@ export function OnboardingForm({ userId, closeDialog }: OnboardingFormProps) { control={form.control} name="graduationMonth" render={({ field }) => ( - + Graduation Month - {/*