Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.

Create navbar and app shell #1

Merged
merged 3 commits into from
Jul 16, 2023
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
2 changes: 1 addition & 1 deletion packages/client/components.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
Expand Down
18 changes: 17 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-portal": "^1.0.3",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@types/node": "20.4.2",
"@types/react": "18.2.15",
"@types/react-dom": "18.2.7",
Expand All @@ -17,14 +24,23 @@
"clsx": "^1.2.1",
"eslint": "8.44.0",
"eslint-config-next": "13.4.9",
"immer": "^10.0.2",
"js-cookie": "^3.0.5",
"jwt-decode": "^3.1.2",
"lucide-react": "^0.260.0",
"next": "13.4.9",
"next-themes": "^0.2.1",
"postcss": "8.4.26",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.10.1",
"tailwind-merge": "^1.13.2",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.6",
"typescript": "5.1.6"
"typescript": "5.1.6",
"zustand": "^4.3.9"
},
"devDependencies": {
"@types/js-cookie": "^3.0.3"
}
}
1 change: 1 addition & 0 deletions packages/client/public/scribble-loop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/client/src/components/icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PiNotePencilThin, PiScribbleLoopFill } from 'react-icons/pi';
import { CreditCard, LogOut, Settings, User } from 'lucide-react';

export const Icons = {
logo: PiScribbleLoopFill,
pencil: PiNotePencilThin,
CreditCard,
LogOut,
Settings,
User,
};
13 changes: 13 additions & 0 deletions packages/client/src/components/layouts/main-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import Navbar from '../navbar';

const MainLayout = ({ children }: React.PropsWithChildren) => {
return (
<>
<Navbar />
<main className="mt-14">{children}</main>
</>
);
};

export default MainLayout;
21 changes: 21 additions & 0 deletions packages/client/src/components/navbar/authed-nav-items.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Link from 'next/link';
import { Icons } from '../icons';
import UserDropdown from './user-dropdown';

const AuthedNavItems = () => {
return (
<>
<Link
className="flex items-center space-x-1 hover:opacity-80"
href={'/scribe-post'}
>
<Icons.pencil className="text-[22px]" />
<span className="text-sm">Scribe</span>
</Link>

<UserDropdown />
</>
);
};

export default AuthedNavItems;
45 changes: 45 additions & 0 deletions packages/client/src/components/navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Icons } from '@/components/icons';
import Link from 'next/link';
import AuthedNavItems from './authed-nav-items';
import { ThemeToggler } from '../theme-toggler';
import { useAuthStore } from '@/lib/stores/auth-store';
import { Button } from '../ui/button';
import { useSSRStore } from '@/lib/stores';

const Navbar = () => {
const isLoggedIn = useSSRStore(useAuthStore, (s) => Boolean(s.jwt));
const login = useAuthStore((s) => s.login);

return (
<header className="w-full bg-slate-600/5 dark:bg-emerald-900/10 border-b fixed top-0 left-0 select-none">
<nav className="container px-2 sm:px-4 md:px-16 py-2 flex justify-between items-center">
<Link href={'/'}>
<Icons.logo className="text-[40px]" />
</Link>

<section className="flex items-center space-x-4 sm:space-x-6 relative">
<ThemeToggler />

{isLoggedIn ? (
<AuthedNavItems />
) : (
<Button
onClick={(e) => {
e.preventDefault();
login(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
);
}}
asChild
size={'sm'}
>
<Link href={'/login'}>Log in</Link>
</Button>
)}
</section>
</nav>
</header>
);
};

export default Navbar;
76 changes: 76 additions & 0 deletions packages/client/src/components/navbar/user-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Icons } from '@/components/icons';
import Link from 'next/link';
import { useAuthStore } from '@/lib/stores/auth-store';

function UserAvatar() {
return (
<Avatar className="h-8 w-8">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback className="font-bold bg-black text-white dark:bg-white dark:text-black">
CN
</AvatarFallback>
</Avatar>
);
}

export default function UserDropdown() {
const logout = useAuthStore((s) => s.logout);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="outline-none">
<UserAvatar />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<section className="px-2 pb-2 pt-1">
<h3 className="text-lg">Developer Ruhul</h3>
<p className="truncate">[email protected]</p>
</section>

<DropdownMenuSeparator />
<div className="my-2">
<DropdownMenuItem className='py-2' asChild>
<Link className="cursor-pointer" href={'/profile'}>
<Icons.User className="mr-2 h-4 w-4" />
<span>Profile</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className='py-2' asChild>
<Link className="cursor-pointer" href={'/settings/billing'}>
<Icons.CreditCard className="mr-2 h-4 w-4" />
<span>Billing</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className='py-2' asChild>
<Link className="cursor-pointer" href={'/settings'}>
<Icons.Settings className="mr-2 h-4 w-4" />
<span>Settings</span>
</Link>
</DropdownMenuItem>
</div>

<DropdownMenuSeparator />

<DropdownMenuItem className='py-2' asChild>
<button
className="outline-none cursor-pointer w-full"
onClick={logout}
>
<Icons.LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
7 changes: 7 additions & 0 deletions packages/client/src/components/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { type ThemeProviderProps } from 'next-themes/dist/types';

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
37 changes: 37 additions & 0 deletions packages/client/src/components/theme-toggler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';

import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';

export function ThemeToggler() {
const { setTheme } = useTheme();

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className='flex outline-none opacity-70'>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
48 changes: 48 additions & 0 deletions packages/client/src/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';

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

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className
)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className
)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;

export { Avatar, AvatarImage, AvatarFallback };
57 changes: 57 additions & 0 deletions packages/client/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

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

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button, buttonVariants }
Loading