diff --git a/bun.lockb b/bun.lockb index 5a92453..4d22c2f 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index feb33aa..0bb922d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@types/canvas-confetti": "^1.6.4", + "canvas-confetti": "^1.9.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "framer-motion": "^11.5.4", diff --git a/src/app/(pages)/dashboard/onboarding/page.tsx b/src/app/(pages)/dashboard/onboarding/page.tsx new file mode 100644 index 0000000..b0dcdbf --- /dev/null +++ b/src/app/(pages)/dashboard/onboarding/page.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { ReactElement } from "react"; +import OnboardingForm from "@/components/dashboard/onboarding/onboarding-form"; +import { useUserContext } from "@/app/provider/user-provider"; +import { UserState } from "@/app/store/user-store-props"; +import { User } from "@/app/types/user/user"; +import { hasFlag } from "@/lib/user"; +import { UserFlag } from "@/app/types/user/user-flag"; +import CompletedOnboarding from "@/components/dashboard/onboarding/completed-onboarding"; + +const OnboardingPage = (): ReactElement => { + const user: User | undefined = useUserContext( + (state: UserState) => state.user + ); + return ( +
+ {hasFlag(user as User, UserFlag.COMPLETED_ONBOARDING) ? ( + + ) : ( + + )} +
+ ); +}; +export default OnboardingPage; diff --git a/src/app/(pages)/dashboard/page.tsx b/src/app/(pages)/dashboard/page.tsx index ea3e3bc..4f203f6 100644 --- a/src/app/(pages)/dashboard/page.tsx +++ b/src/app/(pages)/dashboard/page.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; import { UserState } from "@/app/store/user-store-props"; -import { User } from "@/app/types/user"; +import { User } from "@/app/types/user/user"; import { useUserContext } from "@/app/provider/user-provider"; const DashboardPage = (): ReactElement => { diff --git a/src/app/provider/user-provider.tsx b/src/app/provider/user-provider.tsx index 28ad680..c16df69 100644 --- a/src/app/provider/user-provider.tsx +++ b/src/app/provider/user-provider.tsx @@ -13,14 +13,16 @@ import createUserStore, { UserState, UserStore, } from "@/app/store/user-store-props"; -import { User } from "@/app/types/user"; +import { User } from "@/app/types/user/user"; import { Cookies, useCookies } from "next-client-cookies"; -import { Session } from "@/app/types/session"; +import { Session } from "@/app/types/user/session"; import { apiRequest } from "@/lib/api"; import { StoreApi, useStore } from "zustand"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime"; import DashboardLoader from "@/components/dashboard/loader"; +import { hasFlag } from "@/lib/user"; +import { UserFlag } from "@/app/types/user/user-flag"; /** * The provider that will provide user context to children. @@ -33,6 +35,7 @@ const UserProvider = ({ children }: { children: ReactNode }) => { const [authorized, setAuthorized] = useState(false); const cookies: Cookies = useCookies(); const router: AppRouterInstance = useRouter(); + const path: string = usePathname(); if (!storeRef.current) { storeRef.current = createUserStore(); } @@ -59,9 +62,20 @@ const UserProvider = ({ children }: { children: ReactNode }) => { router.push("/auth"); return; } - storeRef.current?.getState().authorize(session, data as User); + // User successfully authenticated + const user: User = data as User; + storeRef.current?.getState().authorize(session, user); setAuthorized(true); + + // User has not yet completed onboarding + if ( + !hasFlag(user, UserFlag.COMPLETED_ONBOARDING) && + !path.startsWith("/dashboard/onboarding") + ) { + router.push("/dashboard/onboarding"); + } }, [cookies, router]); + useEffect(() => { fetchUser(); }, [fetchUser]); diff --git a/src/app/store/user-store-props.ts b/src/app/store/user-store-props.ts index 3e831fd..08d284f 100644 --- a/src/app/store/user-store-props.ts +++ b/src/app/store/user-store-props.ts @@ -1,7 +1,7 @@ import { createStore } from "zustand"; -import { User } from "@/app/types/user"; +import { User } from "@/app/types/user/user"; import { createContext } from "react"; -import { Session } from "@/app/types/session"; +import { Session } from "@/app/types/user/session"; export const UserContext = createContext(null); diff --git a/src/app/styles/globals.css b/src/app/styles/globals.css index b3bf7d7..fe695a0 100644 --- a/src/app/styles/globals.css +++ b/src/app/styles/globals.css @@ -50,7 +50,7 @@ body { --card-foreground: 0 0% 98%; --popover: 240 10% 3.9%; --popover-foreground: 0 0% 98%; - --primary: 0 84% 60%; + --primary: 0 0% 98%; --primary-foreground: 240 5.9% 10%; --secondary: 240 3.7% 15.9%; --secondary-foreground: 0 0% 98%; diff --git a/src/app/types/onboarding-stage.ts b/src/app/types/onboarding-stage.ts new file mode 100644 index 0000000..f914661 --- /dev/null +++ b/src/app/types/onboarding-stage.ts @@ -0,0 +1,19 @@ +/** + * An onboarding stage. + */ +type OnboardingStage = { + /** + * The name of this stage. + */ + name: string; + + /** + * The description of this stage. + */ + description: string; + + /** + * The form schema for this stage. + */ + schema: any; +}; diff --git a/src/app/types/session.ts b/src/app/types/user/session.ts similarity index 100% rename from src/app/types/session.ts rename to src/app/types/user/session.ts diff --git a/src/app/types/user/user-flag.ts b/src/app/types/user/user-flag.ts new file mode 100644 index 0000000..7efe8f7 --- /dev/null +++ b/src/app/types/user/user-flag.ts @@ -0,0 +1,8 @@ +/** + * Flags for a {@link User}. + */ +export enum UserFlag { + DISABLED = 0, + COMPLETED_ONBOARDING = 1, + ADMINISTRATOR = 2, +} diff --git a/src/app/types/user.ts b/src/app/types/user/user.ts similarity index 100% rename from src/app/types/user.ts rename to src/app/types/user/user.ts diff --git a/src/components/auth/auth-form.tsx b/src/components/auth/auth-form.tsx index 1d458fc..44cb410 100644 --- a/src/components/auth/auth-form.tsx +++ b/src/components/auth/auth-form.tsx @@ -14,7 +14,7 @@ import { LockClosedIcon, } from "@heroicons/react/24/outline"; import { apiRequest } from "@/lib/api"; -import { Session } from "@/app/types/session"; +import { Session } from "@/app/types/user/session"; import { Cookies, useCookies } from "next-client-cookies"; import { useRouter } from "next/navigation"; import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime"; diff --git a/src/components/auth/footer.tsx b/src/components/auth/footer.tsx index 2b97daf..be4aa51 100644 --- a/src/components/auth/footer.tsx +++ b/src/components/auth/footer.tsx @@ -8,7 +8,7 @@ import Link from "next/link"; */ const Footer = (): ReactElement => (