Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -27,10 +29,22 @@ export default function RootLayout({
}>) {
return (
<html
lang="en"
className={cn("h-full", "antialiased", geistSans.variable, geistMono.variable, "font-sans", inter.variable)}
lang='en'
className={cn(
'h-full',
'antialiased',
geistSans.variable,
geistMono.variable,
'font-sans',
inter.variable,
)}
>
<body className="min-h-full flex flex-col">{children}</body>
<body className='min-h-full flex flex-col'>
<UserProvider>
<Navbar />
{children}
</UserProvider>
</body>
</html>
);
}
32 changes: 6 additions & 26 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<main className="min-h-screen">
<div className="flex flex-col min-h-screen items-center justify-center">
<h1 className="text-2xl font-semibold">Kyyros</h1>
<div className="text-lg">
{user ? (
<div>
<p>Welcome, {user.email}!</p>
<form action={signOut}>
<Button variant="outline" type="submit" className="w-full">
Sign Out
</Button>
</form>
</div>
) : (
<p>Not logged in</p>
)}
<main className='min-h-screen'>
<div className='flex flex-col min-h-screen items-center justify-center'>
<h1 className='text-2xl font-semibold'>Kyyros</h1>
<div className='text-lg'>
<p>Welcome!</p>
</div>
</div>

<div className="container mx-auto py-8 px-4">
<div className='container mx-auto py-8 px-4'>
<VideoFeed />
</div>
</main>
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<nav className='flex items-center justify-between py-3 px-4 sticky top-0 z-50 bg-background'>
<Link href='/'>
<span>Kyyros</span>
</Link>

<div className='relative'>
<Search
size={18}
className='absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground'
/>
<Input placeholder='Search...' className='pl-10 w-auto rounded-full' />
</div>

<div>
{isLoading ? null : isAuthenticated && user ? (
<DropdownMenu>
<DropdownMenuTrigger className='cursor-pointer'>
<Avatar>
<AvatarImage />
<AvatarFallback>{user.username?.charAt(0)}</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>

<DropdownMenuContent className='mx-2'>
<DropdownMenuLabel>@{user.username}</DropdownMenuLabel>
<DropdownMenuSeparator />
<div className='*:cursor-pointer'>
<DropdownMenuItem asChild>
<Link href='/'>Profile</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href='/upload'>Upload</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href='/' onClick={handleLogout}>
Log out
</Link>
</DropdownMenuItem>
</div>
</DropdownMenuContent>
</DropdownMenu>
) : (
<div className='space-x-1'>
<Link
href='/login'
className={cn(
buttonVariants({ variant: 'outline' }),
'rounded-full px-3',
)}
>
Log in
</Link>
<Link
href='/signup'
className={cn(
buttonVariants({ variant: 'default' }),
'rounded-full px-3',
)}
>
Sign up
</Link>
</div>
)}
</div>
</nav>
);
}
112 changes: 112 additions & 0 deletions frontend/src/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof AvatarPrimitive.Root> & {
size?: "default" | "sm" | "lg"
}) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
data-size={size}
className={cn(
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
className
)}
{...props}
/>
)
}

function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn(
"aspect-square size-full rounded-full object-cover",
className
)}
{...props}
/>
)
}

function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
className
)}
{...props}
/>
)
}

function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-badge"
className={cn(
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>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 (
<div
data-slot="avatar-group"
className={cn(
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className
)}
{...props}
/>
)
}

function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>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,
}
Loading