Skip to content

Commit

Permalink
[WIP] Feat/frontend/profile (#67)
Browse files Browse the repository at this point in the history
* [frontend] Add skeleton with shadcn
- `npx shadcn-ui@latest add skeleton`

* [frontend] Add a separator with shadcn
- `npx shadcn-ui@latest add separator`

* [frontend] Add profile pages
* [frontend] Fix navbar
---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
usatie and coderabbitai[bot] authored Nov 20, 2023
1 parent 1f74d55 commit b761dcb
Show file tree
Hide file tree
Showing 13 changed files with 321 additions and 26 deletions.
3 changes: 3 additions & 0 deletions frontend/app/profile/account/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AccountPage() {
return <div>Account Page</div>;
}
3 changes: 3 additions & 0 deletions frontend/app/profile/appearance/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AppearancePage() {
return <div>Appearance Page</div>;
}
3 changes: 3 additions & 0 deletions frontend/app/profile/display/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function DisplayPage() {
return <div>Display Page</div>;
}
54 changes: 54 additions & 0 deletions frontend/app/profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Separator } from "@/components/ui/separator";
import { SidebarNav } from "@/app/ui/sidebar-nav";

const sidebarNavItems = [
{
title: "Profile",
href: "/profile",
},
{
title: "Account",
href: "/profile/account",
},
{
title: "Appearance",
href: "/profile/appearance",
},
{
title: "Notifications",
href: "/profile/notifications",
},
{
title: "Display",
href: "/profile/display",
},
];

interface SettingsLayoutProps {
children: React.ReactNode;
}

export default function SettingsLayout({ children }: SettingsLayoutProps) {
return (
<>
<div className="md:hidden">
<div className="space-y-6 p-10 pb-16">Can&apos;t be displayed.</div>
</div>
<div className="hidden pb-16 md:block">
<div className="space-y-0.5">
<h2 className="text-2xl font-bold tracking-tight">Settings</h2>
<p className="text-muted-foreground">
Manage your account settings and set e-mail preferences.
</p>
</div>
<Separator className="my-6" />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
<aside className="lg:w-1/5">
<SidebarNav items={sidebarNavItems} />
</aside>
<div className="flex-1 lg:max-w-2xl">{children}</div>
</div>
</div>
</>
);
}
3 changes: 3 additions & 0 deletions frontend/app/profile/notifications/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function NotificationsPage() {
return <div>Notifications Page</div>;
}
48 changes: 48 additions & 0 deletions frontend/app/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Stack } from "@/app/ui/layout/stack";

function AvatarSkeleton() {
return <Skeleton className="rounded-full h-20 w-20" />;
}

type ProfileItemProps = {
title: string;
value: string;
};

function ProfileItem({ title, value }: ProfileItemProps) {
return (
<Stack spacing={1} className="flex-initial w-96">
<div className="text-xs text-muted-foreground">{title}: </div>
<Input defaultValue={value} />
</Stack>
);
}

export default function ProfilePage() {
// Menu: min 100px
// Profile : the rest
return (
<>
<main>
<form>
<Stack spacing={4}>
<Stack spacing={1}>
<div className="text-2xl">Profile</div>
<Separator />
</Stack>
<AvatarSkeleton />
<ProfileItem title="username" value="susami" />
<ProfileItem title="email" value="[email protected]" />
<Button variant="secondary" className="flex-initial w-40">
Save
</Button>
</Stack>
</form>
</main>
</>
);
}
55 changes: 55 additions & 0 deletions frontend/app/ui/layout/stack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { cn } from "@/lib/utils";

const gaps = {
0: "gap-0",
1: "gap-1",
2: "gap-2",
3: "gap-3",
4: "gap-4",
5: "gap-5",
6: "gap-6",
7: "gap-7",
8: "gap-8",
9: "gap-9",
10: "gap-10",
11: "gap-11",
12: "gap-12",
14: "gap-14",
16: "gap-16",
20: "gap-20",
24: "gap-24",
28: "gap-28",
32: "gap-32",
36: "gap-36",
40: "gap-40",
44: "gap-44",
48: "gap-48",
52: "gap-52",
56: "gap-56",
60: "gap-60",
64: "gap-64",
72: "gap-72",
80: "gap-80",
96: "gap-96",
};

type StackProps = {
className?: string;
spacing?: keyof typeof gaps;
children: React.ReactNode;
};

export function Stack({ className, spacing = 1, children }: StackProps) {
return (
<div className={cn(`flex flex-col ${gaps[spacing]}`, className)}>
{children}
</div>
);
}

// center
export function HStack({ className, spacing = 1, children }: StackProps) {
return (
<div className={cn(`flex ${gaps[spacing]}`, className)}>{children}</div>
);
}
55 changes: 30 additions & 25 deletions frontend/app/ui/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
import Image from "next/image";
import { ModeToggle } from "@/components/toggle-mode";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { signOut } from "@/app/lib/actions";
import { isLoggedIn } from "@/app/lib/session";

export default function Nav() {
function AuthorizedMenu() {
return (
<li className="flex gap-8 items-center">
<Link href="/user">User List</Link>
<Link href="/room">ChatRoom List</Link>
<form action={signOut}>
<Button type="submit">Sign Out</Button>
</form>
<ModeToggle></ModeToggle>
</li>
);
}

function UnauthorizedMenu() {
return (
<li className="flex gap-8 items-center">
<Link href="/user/signup">Sign Up</Link>
<Link href="/login">Log In</Link>
<ModeToggle></ModeToggle>
</li>
);
}

export default async function Nav() {
const isAuthorized = await isLoggedIn();
return (
<header>
<nav>
<ul className="flex items-center justify-between">
<li>
<Image
src="/vercel.svg"
alt="Vercel Logo"
className="dark:invert"
width={300 / 3}
height={68 / 3}
priority
/>
</li>
<li className="flex gap-8 items-center">
<Link href="/">Home</Link>
<Link href="/user">User List</Link>
<Link href="/room">ChatRoom List</Link>
<Link href="/user/signup">Sign Up</Link>
<Link href="/playground/pong.html" target="_blank">
Pong
</Link>
<form action={signOut}>
<Button type="submit">Sign Out</Button>
</form>
<ModeToggle></ModeToggle>
</li>
<Link href="/" className="font-black">
Pong
</Link>
{isAuthorized ? <AuthorizedMenu /> : <UnauthorizedMenu />}
</ul>
</nav>
</header>
Expand Down
44 changes: 44 additions & 0 deletions frontend/app/ui/sidebar-nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";

interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
items: {
href: string;
title: string;
}[];
}

export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
const pathname = usePathname();

return (
<nav
className={cn(
"flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1",
className,
)}
{...props}
>
{items.map((item) => (
<Link
key={item.href}
href={item.href}
className={cn(
buttonVariants({ variant: "ghost" }),
pathname === item.href
? "bg-muted hover:bg-muted"
: "hover:bg-transparent hover:underline",
"justify-start",
)}
>
{item.title}
</Link>
))}
</nav>
);
}
31 changes: 31 additions & 0 deletions frontend/components/ui/separator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";

import { cn } from "@/lib/utils";

const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref,
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className,
)}
{...props}
/>
),
);
Separator.displayName = SeparatorPrimitive.Root.displayName;

export { Separator };
15 changes: 15 additions & 0 deletions frontend/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { cn } from "@/lib/utils";

function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}

export { Skeleton };
Loading

0 comments on commit b761dcb

Please sign in to comment.