diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index f064cba..4e32548 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,9 +1,11 @@ import type { Metadata } from 'next'; import { Geist, Geist_Mono, Inter } from 'next/font/google'; import './globals.css'; -import { cn } from "@/lib/utils"; +import { cn } from '@/lib/utils'; +import { UserProvider } from '@/contexts/user-context'; +import Navbar from '@/components/navbar'; -const inter = Inter({subsets:['latin'],variable:'--font-sans'}); +const inter = Inter({ subsets: ['latin'], variable: '--font-sans' }); const geistSans = Geist({ variable: '--font-geist-sans', @@ -27,10 +29,22 @@ export default function RootLayout({ }>) { return ( - {children} + + + + {children} + + ); } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 98348a6..ca15816 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,36 +1,16 @@ -import { createClient } from '@/lib/supabase/server'; -import { signOut } from '@/actions/actions'; - -import { Button } from '@/components/ui/button'; import VideoFeed from '@/components/video/video-feed'; export default async function Home() { - const supabase = await createClient(); - const { - data: { user }, - } = await supabase.auth.getUser(); - return ( -
-
-

Kyyros

-
- {user ? ( -
-

Welcome, {user.email}!

-
- -
-
- ) : ( -

Not logged in

- )} +
+
+

Kyyros

+
+

Welcome!

-
+
diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx new file mode 100644 index 0000000..5873bad --- /dev/null +++ b/frontend/src/components/navbar.tsx @@ -0,0 +1,100 @@ +'use client'; + +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { Search } from 'lucide-react'; + +import { useUser } from '@/contexts/user-context'; +import { Input } from './ui/input'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from './ui/dropdown-menu'; +import { buttonVariants } from './ui/button'; +import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'; +import { cn } from '@/lib/utils'; +import { createClient } from '@/lib/supabase/client'; + +export default function Navbar() { + const router = useRouter(); + const supabase = createClient(); + + const { user, isLoading, isAuthenticated } = useUser(); + + const handleLogout = async () => { + await supabase.auth.signOut(); + router.push('/'); + }; + + return ( + + ); +} diff --git a/frontend/src/components/ui/avatar.tsx b/frontend/src/components/ui/avatar.tsx new file mode 100644 index 0000000..47ff2e2 --- /dev/null +++ b/frontend/src/components/ui/avatar.tsx @@ -0,0 +1,112 @@ +"use client" + +import * as React from "react" +import { Avatar as AvatarPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" | "lg" +}) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...props} + /> + ) +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + className + )} + {...props} + /> + ) +} + +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarBadge, +} diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..c263ad5 --- /dev/null +++ b/frontend/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,269 @@ +"use client" + +import * as React from "react" +import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { CheckIcon, ChevronRightIcon } from "lucide-react" + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + align = "start", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/frontend/src/contexts/user-context.tsx b/frontend/src/contexts/user-context.tsx index 9baa82e..a3b3314 100644 --- a/frontend/src/contexts/user-context.tsx +++ b/frontend/src/contexts/user-context.tsx @@ -33,7 +33,8 @@ export function UserProvider({ children }: { children: ReactNode }) { try { const userData = await getCurrentUser(controller.signal); setUser(userData); - } catch { + } catch (error) { + console.log('Error fetching user data'); setUser(null); } finally { setIsLoading(false); @@ -49,7 +50,7 @@ export function UserProvider({ children }: { children: ReactNode }) { }); const { data: listener } = supabase.auth.onAuthStateChange((event) => { - if (event === 'SIGNED_IN') { + if (event === 'SIGNED_IN' || event === 'INITIAL_SESSION') { fetchUser(); } else if (event === 'SIGNED_OUT') { setUser(null); diff --git a/frontend/src/lib/api/users.ts b/frontend/src/lib/api/users.ts index e5d8104..c70a3ae 100644 --- a/frontend/src/lib/api/users.ts +++ b/frontend/src/lib/api/users.ts @@ -3,7 +3,7 @@ import type { UserSummary } from '@/types/user'; export function getCurrentUser(signal?: AbortSignal): Promise { return apiFetch( - 'api/v1/users/me', + '/api/v1/users/me', { method: 'GET', signal,