init
This commit is contained in:
commit
a04776256d
56 changed files with 6808 additions and 0 deletions
9
components/theme-provider.tsx
Normal file
9
components/theme-provider.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { ThemeProviderProps } from "next-themes/dist/types";
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
}
|
||||
28
components/theme-toggle.tsx
Normal file
28
components/theme-toggle.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { setTheme, theme } = useTheme();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="p-0 leading-4 h-4 font-normal w-full"
|
||||
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
||||
>
|
||||
<span className="flex flex-row justify-between text-xs text-zinc-900 dark:text-zinc-400 w-full">
|
||||
<span className="">Toggle theme</span>
|
||||
{!theme ? null : theme === "dark" ? (
|
||||
<Moon className="h-4 transition-all" />
|
||||
) : (
|
||||
<Sun className="h-4 transition-all" />
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
52
components/ui/button.tsx
Normal file
52
components/ui/button.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import * as React from "react";
|
||||
import { VariantProps, cva } 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-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "underline-offset-4 hover:underline text-primary",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 py-2 px-4",
|
||||
sm: "h-9 px-3 rounded-md",
|
||||
lg: "h-11 px-8 rounded-md",
|
||||
xs: "h-6 px-3 rounded-md",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
14
components/ui/login.tsx
Normal file
14
components/ui/login.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
export function Login() {
|
||||
return (
|
||||
<Link
|
||||
className="inline-flex w-full items-center justify-center rounded border border-zinc-800 bg-white h-8 px-4 -my-1.5 text-sm leading-6 tracking-tight text-zinc-900 transition-colors ease-in-out hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
href="/api/auth/login?next=/?s=1"
|
||||
>
|
||||
<span className="font-medium">Login</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
97
components/ui/user-menu.tsx
Normal file
97
components/ui/user-menu.tsx
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
"use client";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { type Session } from "next-auth";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import Image from "next/image";
|
||||
|
||||
export interface UserMenuProps {
|
||||
session: Session;
|
||||
}
|
||||
|
||||
export function UserMenu({ session }: UserMenuProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="focus:outline-none">
|
||||
{session.user?.image ? (
|
||||
<Image
|
||||
width={24}
|
||||
height={24}
|
||||
className="h-6 w-6 rounded-full select-none ring-zinc-100/10 ring-1 hover:opacity-80 transition-opacity duration-300"
|
||||
src={session.user?.image ? `${session.user.image}&s=60` : ""}
|
||||
alt={session.user.name ?? "Avatar"}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gradient-to-r from-cyan-500 to-blue-500 p-2 select-none">
|
||||
<div
|
||||
className="font-medium uppercase text-zinc-100"
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{session.user?.name ? session.user?.name.slice(0, 2) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="min-w-[200px] bg-white dark:bg-zinc-950 rounded-lg shadow-lg text-zinc-900 dark:text-zinc-400 overflow-hidden border border-transparent dark:border-zinc-700 focus:outline-none relative z-20 py-2"
|
||||
sideOffset={8}
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenu.Item className="py-2 px-3 focus:outline-none">
|
||||
<div className="text-xs font-medium">{session.user?.name}</div>
|
||||
<div className="text-xs text-zinc-500">{session.user?.email}</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-[1px] bg-zinc-100 dark:bg-zinc-800" />
|
||||
<DropdownMenu.Item className="py-2 px-3 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none">
|
||||
<ThemeToggle />
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-[1px] bg-zinc-100 dark:bg-zinc-800" />
|
||||
<DropdownMenu.Item className="py-2 px-3 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none ">
|
||||
<a
|
||||
href="https://vercel.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-between w-full"
|
||||
>
|
||||
<span>Vercel Homepage</span>
|
||||
<span>
|
||||
<svg
|
||||
fill="none"
|
||||
height={16}
|
||||
shapeRendering="geometricPrecision"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width={16}
|
||||
aria-hidden="true"
|
||||
className="mr-1"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" />
|
||||
<path d="M15 3h6v6" />
|
||||
<path d="M10 14L21 3" />
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="py-2 px-3 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none"
|
||||
onClick={() => {
|
||||
window.location.href = "/api/auth/logout";
|
||||
}}
|
||||
>
|
||||
Log Out
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
UserMenu.displayName = "Signout";
|
||||
14
components/ui/vercel-logo.tsx
Normal file
14
components/ui/vercel-logo.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const VercelLogo = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
height={22}
|
||||
viewBox="0 0 235 203"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Vercel Logo"
|
||||
className={cn(className, "dark:fill-white fill-black")}
|
||||
>
|
||||
<path d="M117.082 0L234.164 202.794H0L117.082 0Z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue