diff --git a/package.json b/package.json index 68e488d..3984f82 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.4", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zustand": "^5.0.0-rc.2" }, "devDependencies": { "@types/eslint": "^8.56.10", diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..e6fe743 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/app/problems/_components/mobile-dropdown.tsx b/src/app/problems/_components/mobile-dropdown.tsx new file mode 100644 index 0000000..45b42df --- /dev/null +++ b/src/app/problems/_components/mobile-dropdown.tsx @@ -0,0 +1,34 @@ +"use client"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu"; +import { MenuIcon } from "lucide-react"; +import { Button } from "~/components/ui/button"; +import { DropdownMenuSeparator } from "~/components/ui/dropdown-menu"; +import { useAuthStore } from "~/providers/auth-store-provider"; + +export function MobileDropdown() { + const session = useAuthStore((state) => state.session); + return ( + + + + + + Profile + Multiplayer + {session && ( + <> + + Sign Out + + )} + + + ); +} diff --git a/src/app/problems/_components/navbar.tsx b/src/app/problems/_components/navbar.tsx new file mode 100644 index 0000000..1670788 --- /dev/null +++ b/src/app/problems/_components/navbar.tsx @@ -0,0 +1,17 @@ +import Link from "next/link"; +import { MobileDropdown } from "./mobile-dropdown"; +import { Button } from "~/components/ui/button"; + +export function Navbar() { + return ( + + ); +} diff --git a/src/app/problems/page.tsx b/src/app/problems/page.tsx new file mode 100644 index 0000000..418f46b --- /dev/null +++ b/src/app/problems/page.tsx @@ -0,0 +1,13 @@ +import { AuthStoreProvider } from "~/providers/auth-store-provider"; +import { Navbar } from "./_components/navbar"; +import { validateRequest } from "~/lib/auth/validate-request"; + +export default async function Problems() { + const auth = await validateRequest(); + return ( + + +
Problems
+
+ ); +} diff --git a/src/providers/auth-store-provider.tsx b/src/providers/auth-store-provider.tsx new file mode 100644 index 0000000..87f42fb --- /dev/null +++ b/src/providers/auth-store-provider.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { createContext, useContext, useRef } from "react"; +import { type validateRequest } from "~/lib/auth/validate-request"; +import { type AuthStore, createAuthStore } from "~/stores/auth-store"; +import { useStore } from "zustand"; + +export type AuthStoreApi = ReturnType; + +export const AuthStoreContext = createContext( + undefined, +); + +export function AuthStoreProvider({ + children, + auth, +}: { + children: React.ReactNode; + auth: Awaited>; +}) { + const storeRef = useRef(); + if (!storeRef.current) { + storeRef.current = createAuthStore(auth); + } + + return ( + + {children} + + ); +} + +export function useAuthStore(selector: (state: AuthStore) => T): T { + const authStore = useContext(AuthStoreContext); + if (!authStore) { + throw new Error("useAuthStore must be used within an AuthStoreProvider"); + } + return useStore(authStore, selector); +} diff --git a/src/stores/auth-store.ts b/src/stores/auth-store.ts new file mode 100644 index 0000000..a84fbac --- /dev/null +++ b/src/stores/auth-store.ts @@ -0,0 +1,19 @@ +import { createStore } from "zustand/vanilla"; +import { type validateRequest } from "~/lib/auth/validate-request"; + +export type AuthState = Awaited>; + +export type AuthStore = AuthState; + +export const defaultAuthState: AuthState = { + user: null, + session: null, +}; + +export const createAuthStore = ( + initialAuthState: AuthState = defaultAuthState, +) => { + return createStore()(() => ({ + ...initialAuthState, + })); +};