diff options
Diffstat (limited to 'web')
46 files changed, 0 insertions, 2068 deletions
diff --git a/web/.env.sample b/web/.env.sample deleted file mode 100644 index a48054f0..00000000 --- a/web/.env.sample +++ /dev/null @@ -1,8 +0,0 @@ -DATABASE_URL="file:./dev.db" -NEXTAUTH_URL= -NEXTAUTH_SECRET= - -# Oauth -AUTHENTIK_ID= -AUTHENTIK_SECRET= -AUTHENTIK_ISSUER= diff --git a/web/Dockerfile b/web/Dockerfile deleted file mode 100644 index 30a46bd3..00000000 --- a/web/Dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -FROM oven/bun:1.0-alpine AS base - -# Install dependencies only when needed -FROM base AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -# RUN apk add --no-cache libc6-compat -WORKDIR /app - -# Install dependencies based on the preferred package manager -COPY package.json bun.lockb ./ -RUN bun install --frozen-lockfile - -# Rebuild the source code only when needed -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -ENV NODE_ENV production - -ENV NEXT_TELEMETRY_DISABLED 1 - -RUN bun run build - -# Production image, copy all the files and run next -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV production - -ENV NEXT_TELEMETRY_DISABLED 1 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public - -# Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:nodejs .next - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - -USER nextjs - -EXPOSE 3000 - -ENV PORT 3000 -# set hostname to localhost -ENV HOSTNAME "0.0.0.0" - -# server.js is created by next build from the standalone output -# https://nextjs.org/docs/pages/api-reference/next-config-js/output -CMD ["node", "server.js"] diff --git a/web/README.md b/web/README.md deleted file mode 100644 index c4033664..00000000 --- a/web/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/web/app/api/auth/[...nextauth]/route.tsx b/web/app/api/auth/[...nextauth]/route.tsx deleted file mode 100644 index e722926b..00000000 --- a/web/app/api/auth/[...nextauth]/route.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { authHandler } from "@/lib/auth"; - -export { authHandler as GET, authHandler as POST }; diff --git a/web/app/api/v1/links/[linkId]/route.ts b/web/app/api/v1/links/[linkId]/route.ts deleted file mode 100644 index 39449d6d..00000000 --- a/web/app/api/v1/links/[linkId]/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { authOptions } from "@/lib/auth"; -import { unbookmarkLink } from "@/lib/services/links"; -import { Prisma } from "@remember/db"; - -import { getServerSession } from "next-auth"; -import { NextRequest } from "next/server"; - -export async function DELETE( - _request: NextRequest, - { params }: { params: { linkId: string } }, -) { - // TODO: We probably should be using an API key here instead of the session; - const session = await getServerSession(authOptions); - if (!session) { - return new Response(null, { status: 401 }); - } - - try { - await unbookmarkLink(params.linkId, session.user.id); - } catch (e: unknown) { - if ( - e instanceof Prisma.PrismaClientKnownRequestError && - e.code === "P2025" // RecordNotFound - ) { - return new Response(null, { status: 404 }); - } else { - throw e; - } - } - - return new Response(null, { status: 201 }); -} diff --git a/web/app/api/v1/links/route.ts b/web/app/api/v1/links/route.ts deleted file mode 100644 index 87541634..00000000 --- a/web/app/api/v1/links/route.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { authOptions } from "@/lib/auth"; -import { bookmarkLink, getLinks } from "@/lib/services/links"; - -import { - zNewBookmarkedLinkRequestSchema, - ZGetLinksResponse, - ZBookmarkedLink, -} from "@/lib/types/api/links"; -import { getServerSession } from "next-auth"; -import { NextRequest, NextResponse } from "next/server"; - -export async function POST(request: NextRequest) { - // TODO: We probably should be using an API key here instead of the session; - const session = await getServerSession(authOptions); - if (!session) { - return new Response(null, { status: 401 }); - } - - const linkRequest = zNewBookmarkedLinkRequestSchema.safeParse( - await request.json(), - ); - - if (!linkRequest.success) { - return NextResponse.json( - { - error: linkRequest.error.toString(), - }, - { status: 400 }, - ); - } - - const link = await bookmarkLink(linkRequest.data.url, session.user.id); - - let response: ZBookmarkedLink = { ...link }; - return NextResponse.json(response, { status: 201 }); -} - -export async function GET() { - // TODO: We probably should be using an API key here instead of the session; - const session = await getServerSession(authOptions); - if (!session) { - return new Response(null, { status: 401 }); - } - - const links = await getLinks(session.user.id); - - let response: ZGetLinksResponse = { links }; - return NextResponse.json(response); -} diff --git a/web/app/dashboard/bookmarks/components/AddLink.tsx b/web/app/dashboard/bookmarks/components/AddLink.tsx deleted file mode 100644 index fb77786c..00000000 --- a/web/app/dashboard/bookmarks/components/AddLink.tsx +++ /dev/null @@ -1,67 +0,0 @@ -"use client"; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import APIClient from "@/lib/api"; -import { Plus } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useForm, SubmitErrorHandler } from "react-hook-form"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { toast } from "@/components/ui/use-toast"; - -const formSchema = z.object({ - url: z.string().url({ message: "The link must be a valid URL" }), -}); - -export default function AddLink() { - const router = useRouter(); - - const form = useForm<z.infer<typeof formSchema>>({ - resolver: zodResolver(formSchema), - }); - - async function onSubmit(value: z.infer<typeof formSchema>) { - const [_resp, error] = await APIClient.bookmarkLink(value.url); - if (error) { - toast({ description: error.message, variant: "destructive" }); - return; - } - router.refresh(); - } - - const onError: SubmitErrorHandler<z.infer<typeof formSchema>> = (errors) => { - toast({ - description: Object.values(errors) - .map((v) => v.message) - .join("\n"), - variant: "destructive", - }); - }; - - return ( - <Form {...form}> - <form onSubmit={form.handleSubmit(onSubmit, onError)}> - <div className="py-4 container flex w-full items-center space-x-2"> - <FormField - control={form.control} - name="url" - render={({ field }) => { - return ( - <FormItem className="flex-1"> - <FormControl> - <Input type="text" placeholder="Link" {...field} /> - </FormControl> - </FormItem> - ); - }} - /> - <Button type="submit"> - <Plus /> - </Button> - </div> - </form> - </Form> - ); -} diff --git a/web/app/dashboard/bookmarks/components/LinkCard.tsx b/web/app/dashboard/bookmarks/components/LinkCard.tsx deleted file mode 100644 index da59d9da..00000000 --- a/web/app/dashboard/bookmarks/components/LinkCard.tsx +++ /dev/null @@ -1,96 +0,0 @@ -"use client"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - ImageCard, - ImageCardBody, - ImageCardFooter, - ImageCardTitle, -} from "@/components/ui/imageCard"; -import { useToast } from "@/components/ui/use-toast"; -import APIClient from "@/lib/api"; -import { ZBookmarkedLink } from "@/lib/types/api/links"; -import { MoreHorizontal, Trash2 } from "lucide-react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; - -export function LinkOptions({ linkId }: { linkId: string }) { - const { toast } = useToast(); - const router = useRouter(); - - const unbookmarkLink = async () => { - let [_, error] = await APIClient.unbookmarkLink(linkId); - - if (error) { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "There was a problem with your request.", - }); - } else { - toast({ - description: "The link has been deleted!", - }); - } - - router.refresh(); - }; - return ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="ghost"> - <MoreHorizontal /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent className="w-fit"> - <DropdownMenuItem className="text-destructive" onClick={unbookmarkLink}> - <Trash2 className="mr-2 h-4 w-4" /> - <span>Delete</span> - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - ); -} - -export default function LinkCard({ link }: { link: ZBookmarkedLink }) { - const parsedUrl = new URL(link.url); - - return ( - <ImageCard - className={ - "bg-gray-50 duration-300 ease-in border border-grey-100 hover:transition-all hover:border-blue-300" - } - image={link.details?.imageUrl ?? undefined} - > - <ImageCardTitle> - <Link className="line-clamp-3" href={link.url}> - {link.details?.title ?? parsedUrl.host} - </Link> - </ImageCardTitle> - <ImageCardBody className="py-2 overflow-clip"> - {link.tags.map((t) => ( - <Badge variant="default" className="bg-gray-300 text-gray-500" key={t.id}> - #{t.name} - </Badge> - ))} - </ImageCardBody> - <ImageCardFooter> - <div className="flex justify-between text-gray-500"> - <div className="my-auto"> - <Link className="line-clamp-1 hover:text-black" href={link.url}> - {parsedUrl.host} - </Link> - </div> - <LinkOptions linkId={link.id} /> - </div> - </ImageCardFooter> - </ImageCard> - ); -} diff --git a/web/app/dashboard/bookmarks/components/LinksGrid.tsx b/web/app/dashboard/bookmarks/components/LinksGrid.tsx deleted file mode 100644 index 66f0d766..00000000 --- a/web/app/dashboard/bookmarks/components/LinksGrid.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { getServerSession } from "next-auth"; -import { redirect } from "next/navigation"; -import { authOptions } from "@/lib/auth"; -import { getLinks } from "@/lib/services/links"; -import LinkCard from "./LinkCard"; - -export default async function LinksGrid() { - const session = await getServerSession(authOptions); - if (!session) { - redirect("/"); - } - const links = await getLinks(session.user.id); - - return ( - <div className="container grid gap-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4"> - {links.map((l) => ( - <LinkCard key={l.id} link={l} /> - ))} - </div> - ); -} diff --git a/web/app/dashboard/bookmarks/page.tsx b/web/app/dashboard/bookmarks/page.tsx deleted file mode 100644 index b4158893..00000000 --- a/web/app/dashboard/bookmarks/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import AddLink from "./components/AddLink"; -import LinksGrid from "./components/LinksGrid"; -import type { Metadata } from "next"; - -export const metadata: Metadata = { - title: "Remember - Bookmarks", -}; - -export default async function Bookmarks() { - return ( - <div className="flex flex-col"> - <div> - <AddLink /> - </div> - <div> - <LinksGrid /> - </div> - </div> - ); -} diff --git a/web/app/dashboard/components/Sidebar.tsx b/web/app/dashboard/components/Sidebar.tsx deleted file mode 100644 index 0ed87daf..00000000 --- a/web/app/dashboard/components/Sidebar.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { authOptions } from "@/lib/auth"; -import { Archive, MoreHorizontal, Star, Tag, Home, Brain} from "lucide-react"; -import { getServerSession } from "next-auth"; -import Link from "next/link"; -import { redirect } from "next/navigation"; - -function SidebarItem({ - name, - logo, - path, -}: { - name: string; - logo: React.ReactNode; - path: string; -}) { - return ( - <li className="rounded-lg px-3 py-2 hover:bg-slate-100"> - <Link href={path} className="flex w-full space-x-2"> - {logo} - <span className="my-auto"> {name} </span> - </Link> - </li> - ); -} - -export default async function Sidebar() { - const session = await getServerSession(authOptions); - if (!session) { - redirect("/"); - } - - return ( - <aside className="flex flex-col h-full w-60 border-r p-4"> - <div className="flex px-1 mb-5 items-center rounded-lg text-slate-900"> - <Brain /> - <span className="ml-2 text-base font-semibold">Remember</span> - </div> - <hr /> - <div> - <ul className="space-y-2 mt-5 text-sm font-medium"> - <SidebarItem logo={<Home />} name="Home" path="#" /> - <SidebarItem logo={<Star />} name="Favourites" path="#" /> - <SidebarItem logo={<Archive />} name="Archived" path="#" /> - <SidebarItem logo={<Tag />} name="Tags" path="#" /> - </ul> - </div> - <div className="mt-auto flex justify-between"> - <div className="my-auto"> {session.user.name} </div> - <Button variant="ghost" className="h-10 w-30"> - <MoreHorizontal /> - </Button> - </div> - </aside> - ); -} diff --git a/web/app/dashboard/layout.tsx b/web/app/dashboard/layout.tsx deleted file mode 100644 index 9b21271e..00000000 --- a/web/app/dashboard/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import Bookmarks from "@/app/dashboard/bookmarks/page"; -import Sidebar from "@/app/dashboard/components/Sidebar"; - -export default async function Dashboard() { - return ( - <div className="flex w-screen h-screen"> - <div className="flex-none"> - <Sidebar /> - </div> - <div className="flex-1 bg-gray-100"> - <Bookmarks /> - </div> - </div> - ); -} diff --git a/web/app/favicon.ico b/web/app/favicon.ico Binary files differdeleted file mode 100644 index 718d6fea..00000000 --- a/web/app/favicon.ico +++ /dev/null diff --git a/web/app/globals.css b/web/app/globals.css deleted file mode 100644 index 8abdb15c..00000000 --- a/web/app/globals.css +++ /dev/null @@ -1,76 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - } -} - -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } -} diff --git a/web/app/layout.tsx b/web/app/layout.tsx deleted file mode 100644 index a2d34046..00000000 --- a/web/app/layout.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "./globals.css"; -import React from "react"; -import { Toaster } from "@/components/ui/toaster"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Remember", - description: "Your AI powered second brain", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - <html lang="en"> - <body className={inter.className}> - {children} - <Toaster /> - </body> - </html> - ); -} diff --git a/web/app/page.tsx b/web/app/page.tsx deleted file mode 100644 index ffc128a5..00000000 --- a/web/app/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { LoginButton } from "@/components/auth/login"; -import { LogoutButton } from "@/components/auth/logout"; -import Link from "next/link"; - -export default function Home() { - return ( - <main className="flex min-h-screen flex-col items-center justify-between p-24"> - <div> - <LoginButton /> - <br /> - <br /> - <LogoutButton /> - <br /> - <br /> - <Link href="/bookmarks">Bookmarks</Link> - </div> - </main> - ); -} diff --git a/web/bun.lockb b/web/bun.lockb Binary files differdeleted file mode 100755 index 7925e942..00000000 --- a/web/bun.lockb +++ /dev/null diff --git a/web/components.json b/web/components.json deleted file mode 100644 index fa674c93..00000000 --- a/web/components.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "tailwind.config.ts", - "css": "app/globals.css", - "baseColor": "slate", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils" - } -} diff --git a/web/components/auth/login.tsx b/web/components/auth/login.tsx deleted file mode 100644 index 4cd55546..00000000 --- a/web/components/auth/login.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; -import { signIn } from "next-auth/react"; - -export const LoginButton = () => { - return ( - <button - className="btn btn-primary" - onClick={() => - signIn(undefined, { - callbackUrl: "/", - }) - } - > - Sign in - </button> - ); -}; diff --git a/web/components/auth/logout.tsx b/web/components/auth/logout.tsx deleted file mode 100644 index 8d627f68..00000000 --- a/web/components/auth/logout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; -import { signOut } from "next-auth/react"; - -export const LogoutButton = () => { - return ( - <button - className="btn btn-ghost normal-case" - onClick={() => - signOut({ - callbackUrl: "/", - }) - } - > - Sign Out - </button> - ); -}; diff --git a/web/components/ui/badge.tsx b/web/components/ui/badge.tsx deleted file mode 100644 index d3d5d604..00000000 --- a/web/components/ui/badge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -export interface BadgeProps - extends React.HTMLAttributes<HTMLDivElement>, - VariantProps<typeof badgeVariants> {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( - <div className={cn(badgeVariants({ variant }), className)} {...props} /> - ); -} - -export { Badge, badgeVariants }; diff --git a/web/components/ui/button.tsx b/web/components/ui/button.tsx deleted file mode 100644 index 57c9fe47..00000000 --- a/web/components/ui/button.tsx +++ /dev/null @@ -1,56 +0,0 @@ -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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", - { - 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 bg-background 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: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - 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 }; diff --git a/web/components/ui/card.tsx b/web/components/ui/card.tsx deleted file mode 100644 index dc3b01de..00000000 --- a/web/components/ui/card.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div - ref={ref} - className={cn( - "rounded-lg border bg-card text-card-foreground shadow-sm", - className, - )} - {...props} - /> -)); -Card.displayName = "Card"; - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div - ref={ref} - className={cn("flex flex-col space-y-1.5 p-6", className)} - {...props} - /> -)); -CardHeader.displayName = "CardHeader"; - -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes<HTMLHeadingElement> ->(({ className, ...props }, ref) => ( - <h3 - ref={ref} - className={cn( - "text-2xl font-semibold leading-none tracking-tight", - className, - )} - {...props} - /> -)); -CardTitle.displayName = "CardTitle"; - -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes<HTMLParagraphElement> ->(({ className, ...props }, ref) => ( - <p - ref={ref} - className={cn("text-sm text-muted-foreground", className)} - {...props} - /> -)); -CardDescription.displayName = "CardDescription"; - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> -)); -CardContent.displayName = "CardContent"; - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => ( - <div - ref={ref} - className={cn("flex items-center p-6 pt-0", className)} - {...props} - /> -)); -CardFooter.displayName = "CardFooter"; - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -}; diff --git a/web/components/ui/dropdown-menu.tsx b/web/components/ui/dropdown-menu.tsx deleted file mode 100644 index 3a0c7fed..00000000 --- a/web/components/ui/dropdown-menu.tsx +++ /dev/null @@ -1,200 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; -import { Check, ChevronRight, Circle } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const DropdownMenu = DropdownMenuPrimitive.Root; - -const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; - -const DropdownMenuGroup = DropdownMenuPrimitive.Group; - -const DropdownMenuPortal = DropdownMenuPrimitive.Portal; - -const DropdownMenuSub = DropdownMenuPrimitive.Sub; - -const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; - -const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { - inset?: boolean; - } ->(({ className, inset, children, ...props }, ref) => ( - <DropdownMenuPrimitive.SubTrigger - ref={ref} - className={cn( - "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent", - inset && "pl-8", - className, - )} - {...props} - > - {children} - <ChevronRight className="ml-auto h-4 w-4" /> - </DropdownMenuPrimitive.SubTrigger> -)); -DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName; - -const DropdownMenuSubContent = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> ->(({ className, ...props }, ref) => ( - <DropdownMenuPrimitive.SubContent - ref={ref} - className={cn( - "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", - className, - )} - {...props} - /> -)); -DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName; - -const DropdownMenuContent = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Content>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> ->(({ className, sideOffset = 4, ...props }, ref) => ( - <DropdownMenuPrimitive.Portal> - <DropdownMenuPrimitive.Content - ref={ref} - sideOffset={sideOffset} - className={cn( - "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", - className, - )} - {...props} - /> - </DropdownMenuPrimitive.Portal> -)); -DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; - -const DropdownMenuItem = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Item>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - <DropdownMenuPrimitive.Item - ref={ref} - className={cn( - "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - inset && "pl-8", - className, - )} - {...props} - /> -)); -DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; - -const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> ->(({ className, children, checked, ...props }, ref) => ( - <DropdownMenuPrimitive.CheckboxItem - ref={ref} - className={cn( - "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - className, - )} - checked={checked} - {...props} - > - <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> - <DropdownMenuPrimitive.ItemIndicator> - <Check className="h-4 w-4" /> - </DropdownMenuPrimitive.ItemIndicator> - </span> - {children} - </DropdownMenuPrimitive.CheckboxItem> -)); -DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName; - -const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> ->(({ className, children, ...props }, ref) => ( - <DropdownMenuPrimitive.RadioItem - ref={ref} - className={cn( - "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", - className, - )} - {...props} - > - <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> - <DropdownMenuPrimitive.ItemIndicator> - <Circle className="h-2 w-2 fill-current" /> - </DropdownMenuPrimitive.ItemIndicator> - </span> - {children} - </DropdownMenuPrimitive.RadioItem> -)); -DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; - -const DropdownMenuLabel = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Label>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { - inset?: boolean; - } ->(({ className, inset, ...props }, ref) => ( - <DropdownMenuPrimitive.Label - ref={ref} - className={cn( - "px-2 py-1.5 text-sm font-semibold", - inset && "pl-8", - className, - )} - {...props} - /> -)); -DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; - -const DropdownMenuSeparator = React.forwardRef< - React.ElementRef<typeof DropdownMenuPrimitive.Separator>, - React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> ->(({ className, ...props }, ref) => ( - <DropdownMenuPrimitive.Separator - ref={ref} - className={cn("-mx-1 my-1 h-px bg-muted", className)} - {...props} - /> -)); -DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; - -const DropdownMenuShortcut = ({ - className, - ...props -}: React.HTMLAttributes<HTMLSpanElement>) => { - return ( - <span - className={cn("ml-auto text-xs tracking-widest opacity-60", className)} - {...props} - /> - ); -}; -DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; - -export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, -}; diff --git a/web/components/ui/form.tsx b/web/components/ui/form.tsx deleted file mode 100644 index 4603f8b3..00000000 --- a/web/components/ui/form.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { Slot } from "@radix-ui/react-slot" -import { - Controller, - ControllerProps, - FieldPath, - FieldValues, - FormProvider, - useFormContext, -} from "react-hook-form" - -import { cn } from "@/lib/utils" -import { Label } from "@/components/ui/label" - -const Form = FormProvider - -type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> -> = { - name: TName -} - -const FormFieldContext = React.createContext<FormFieldContextValue>( - {} as FormFieldContextValue -) - -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> ->({ - ...props -}: ControllerProps<TFieldValues, TName>) => { - return ( - <FormFieldContext.Provider value={{ name: props.name }}> - <Controller {...props} /> - </FormFieldContext.Provider> - ) -} - -const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext) - const itemContext = React.useContext(FormItemContext) - const { getFieldState, formState } = useFormContext() - - const fieldState = getFieldState(fieldContext.name, formState) - - if (!fieldContext) { - throw new Error("useFormField should be used within <FormField>") - } - - const { id } = itemContext - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - } -} - -type FormItemContextValue = { - id: string -} - -const FormItemContext = React.createContext<FormItemContextValue>( - {} as FormItemContextValue -) - -const FormItem = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->(({ className, ...props }, ref) => { - const id = React.useId() - - return ( - <FormItemContext.Provider value={{ id }}> - <div ref={ref} className={cn("space-y-2", className)} {...props} /> - </FormItemContext.Provider> - ) -}) -FormItem.displayName = "FormItem" - -const FormLabel = React.forwardRef< - React.ElementRef<typeof LabelPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> ->(({ className, ...props }, ref) => { - const { error, formItemId } = useFormField() - - return ( - <Label - ref={ref} - className={cn(error && "text-destructive", className)} - htmlFor={formItemId} - {...props} - /> - ) -}) -FormLabel.displayName = "FormLabel" - -const FormControl = React.forwardRef< - React.ElementRef<typeof Slot>, - React.ComponentPropsWithoutRef<typeof Slot> ->(({ ...props }, ref) => { - const { error, formItemId, formDescriptionId, formMessageId } = useFormField() - - return ( - <Slot - ref={ref} - id={formItemId} - aria-describedby={ - !error - ? `${formDescriptionId}` - : `${formDescriptionId} ${formMessageId}` - } - aria-invalid={!!error} - {...props} - /> - ) -}) -FormControl.displayName = "FormControl" - -const FormDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes<HTMLParagraphElement> ->(({ className, ...props }, ref) => { - const { formDescriptionId } = useFormField() - - return ( - <p - ref={ref} - id={formDescriptionId} - className={cn("text-sm text-muted-foreground", className)} - {...props} - /> - ) -}) -FormDescription.displayName = "FormDescription" - -const FormMessage = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes<HTMLParagraphElement> ->(({ className, children, ...props }, ref) => { - const { error, formMessageId } = useFormField() - const body = error ? String(error?.message) : children - - if (!body) { - return null - } - - return ( - <p - ref={ref} - id={formMessageId} - className={cn("text-sm font-medium text-destructive", className)} - {...props} - > - {body} - </p> - ) -}) -FormMessage.displayName = "FormMessage" - -export { - useFormField, - Form, - FormItem, - FormLabel, - FormControl, - FormDescription, - FormMessage, - FormField, -} diff --git a/web/components/ui/imageCard.tsx b/web/components/ui/imageCard.tsx deleted file mode 100644 index 1394ae08..00000000 --- a/web/components/ui/imageCard.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -export function ImageCard({ - children, - image, - className, - ...props -}: React.HTMLAttributes<HTMLDivElement> & { image?: string }) { - return ( - <div - className={cn("h-96 rounded-lg overflow-hidden shadow-md", className)} - {...props} - > - <div - className="h-3/5 bg-cover bg-center" - style={{ - backgroundImage: image ? `url(${image})` : undefined, - }} - ></div> - <div className="flex flex-col h-2/5 p-2">{children}</div> - </div> - ); -} - -export function ImageCardTitle({ - className, - ...props -}: React.HTMLAttributes<HTMLDivElement>) { - return ( - <div - className={cn("order-first flex-none font-bold text-lg", className)} - {...props} - /> - ); -} - -export function ImageCardBody({ - className, - ...props -}: React.HTMLAttributes<HTMLDivElement>) { - return ( - <div - className={cn("grow order-1 font-bold text-lg", className)} - {...props} - /> - ); -} - -export function ImageCardFooter({ - className, - ...props -}: React.HTMLAttributes<HTMLDivElement>) { - return <div className={cn("order-last", className)} {...props} />; -} diff --git a/web/components/ui/input.tsx b/web/components/ui/input.tsx deleted file mode 100644 index 9d631e7f..00000000 --- a/web/components/ui/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -export interface InputProps - extends React.InputHTMLAttributes<HTMLInputElement> {} - -const Input = React.forwardRef<HTMLInputElement, InputProps>( - ({ className, type, ...props }, ref) => { - return ( - <input - type={type} - className={cn( - "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", - className, - )} - ref={ref} - {...props} - /> - ); - }, -); -Input.displayName = "Input"; - -export { Input }; diff --git a/web/components/ui/label.tsx b/web/components/ui/label.tsx deleted file mode 100644 index 53418217..00000000 --- a/web/components/ui/label.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client" - -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) - -const Label = React.forwardRef< - React.ElementRef<typeof LabelPrimitive.Root>, - React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & - VariantProps<typeof labelVariants> ->(({ className, ...props }, ref) => ( - <LabelPrimitive.Root - ref={ref} - className={cn(labelVariants(), className)} - {...props} - /> -)) -Label.displayName = LabelPrimitive.Root.displayName - -export { Label } diff --git a/web/components/ui/toast.tsx b/web/components/ui/toast.tsx deleted file mode 100644 index 2bc23c1f..00000000 --- a/web/components/ui/toast.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import * as React from "react"; -import * as ToastPrimitives from "@radix-ui/react-toast"; -import { cva, type VariantProps } from "class-variance-authority"; -import { X } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const ToastProvider = ToastPrimitives.Provider; - -const ToastViewport = React.forwardRef< - React.ElementRef<typeof ToastPrimitives.Viewport>, - React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> ->(({ className, ...props }, ref) => ( - <ToastPrimitives.Viewport - ref={ref} - className={cn( - "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", - className, - )} - {...props} - /> -)); -ToastViewport.displayName = ToastPrimitives.Viewport.displayName; - -const toastVariants = cva( - "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", - { - variants: { - variant: { - default: "border bg-background text-foreground", - destructive: - "destructive group border-destructive bg-destructive text-destructive-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -const Toast = React.forwardRef< - React.ElementRef<typeof ToastPrimitives.Root>, - React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & - VariantProps<typeof toastVariants> ->(({ className, variant, ...props }, ref) => { - return ( - <ToastPrimitives.Root - ref={ref} - className={cn(toastVariants({ variant }), className)} - {...props} - /> - ); -}); -Toast.displayName = ToastPrimitives.Root.displayName; - -const ToastAction = React.forwardRef< - React.ElementRef<typeof ToastPrimitives.Action>, - React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> ->(({ className, ...props }, ref) => ( - <ToastPrimitives.Action - ref={ref} - className={cn( - "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", - className, - )} - {...props} - /> -)); -ToastAction.displayName = ToastPrimitives.Action.displayName; - -const ToastClose = React.forwardRef< - React.ElementRef<typeof ToastPrimitives.Close>, - React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> ->(({ className, ...props }, ref) => ( - <ToastPrimitives.Close - ref={ref} - className={cn( - "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", - className, - )} - toast-close="" - {...props} - > - <X className="h-4 w-4" /> - </ToastPrimitives.Close> -)); -ToastClose.displayName = ToastPrimitives.Close.displayName; - -const ToastTitle = React.forwardRef< - React.ElementRef<typeof ToastPrimitives.Title>, - React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> ->(({ className, ...props }, ref) => ( - <ToastPrimitives.Title - ref={ref} - className={cn("text-sm font-semibold", className)} - {...props} - /> -)); -ToastTitle.displayName = ToastPrimitives.Title.displayName; - -const ToastDescription = React.forwardRef< - React.ElementRef<typeof ToastPrimitives.Description>, - React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> ->(({ className, ...props }, ref) => ( - <ToastPrimitives.Description - ref={ref} - className={cn("text-sm opacity-90", className)} - {...props} - /> -)); -ToastDescription.displayName = ToastPrimitives.Description.displayName; - -type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>; - -type ToastActionElement = React.ReactElement<typeof ToastAction>; - -export { - type ToastProps, - type ToastActionElement, - ToastProvider, - ToastViewport, - Toast, - ToastTitle, - ToastDescription, - ToastClose, - ToastAction, -}; diff --git a/web/components/ui/toaster.tsx b/web/components/ui/toaster.tsx deleted file mode 100644 index 7d82ed55..00000000 --- a/web/components/ui/toaster.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { - Toast, - ToastClose, - ToastDescription, - ToastProvider, - ToastTitle, - ToastViewport, -} from "@/components/ui/toast"; -import { useToast } from "@/components/ui/use-toast"; - -export function Toaster() { - const { toasts } = useToast(); - - return ( - <ToastProvider> - {toasts.map(function ({ id, title, description, action, ...props }) { - return ( - <Toast key={id} {...props}> - <div className="grid gap-1"> - {title && <ToastTitle>{title}</ToastTitle>} - {description && ( - <ToastDescription>{description}</ToastDescription> - )} - </div> - {action} - <ToastClose /> - </Toast> - ); - })} - <ToastViewport /> - </ToastProvider> - ); -} diff --git a/web/components/ui/use-toast.ts b/web/components/ui/use-toast.ts deleted file mode 100644 index 5491e140..00000000 --- a/web/components/ui/use-toast.ts +++ /dev/null @@ -1,189 +0,0 @@ -// Inspired by react-hot-toast library -import * as React from "react"; - -import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; - -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; - -type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; - -const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; - -let count = 0; - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -} - -type ActionType = typeof actionTypes; - -type Action = - | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } - | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial<ToasterToast>; - } - | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } - | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; - -interface State { - toasts: ToasterToast[]; -} - -const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>(); - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return; - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); - - toastTimeouts.set(toastId, timeout); -}; - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - }; - - case "DISMISS_TOAST": { - const { toastId } = action; - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId); - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - }; - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - }; - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; - } -}; - -const listeners: Array<(_state: State) => void> = []; - -let memoryState: State = { toasts: [] }; - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action); - listeners.forEach((listener) => { - listener(memoryState); - }); -} - -type Toast = Omit<ToasterToast, "id">; - -function toast({ ...props }: Toast) { - const id = genId(); - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); - }, - }, - }); - - return { - id: id, - dismiss, - update, - }; -} - -function useToast() { - const [state, setState] = React.useState<State>(memoryState); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) { - listeners.splice(index, 1); - } - }; - }, [state]); - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; -} - -export { useToast, toast }; diff --git a/web/lib/api.ts b/web/lib/api.ts deleted file mode 100644 index 56686cde..00000000 --- a/web/lib/api.ts +++ /dev/null @@ -1,81 +0,0 @@ -"use client"; - -import { ZodTypeAny, z } from "zod"; -import { - ZNewBookmarkedLinkRequest, - zGetLinksResponseSchema, -} from "./types/api/links"; - -import serverConfig from "./config"; - -const BASE_URL = `${serverConfig.api_url}/api/v1`; - -export type FetchError = { - status?: number; - message?: string; -}; - -type InputSchema<T> = T extends ZodTypeAny ? T : undefined; - -async function doRequest<T>( - path: string, - respSchema?: InputSchema<T>, - opts?: RequestInit, -): Promise< - | (InputSchema<T> extends ZodTypeAny - ? [z.infer<InputSchema<T>>, undefined] - : [undefined, undefined]) - | [undefined, FetchError] -> { - try { - const res = await fetch(`${BASE_URL}${path}`, opts); - if (!res.ok) { - return [ - undefined, - { status: res.status, message: await res.text() }, - ] as const; - } - if (!respSchema) { - return [undefined, undefined] as const; - } - - let parsed = respSchema.safeParse(await res.json()); - if (!parsed.success) { - return [ - undefined, - { message: `Failed to parse response: ${parsed.error.toString()}` }, - ] as const; - } - - return [parsed.data, undefined] as const; - } catch (error: any) { - return [ - undefined, - { message: `Failed to execute fetch request: ${error}` }, - ] as const; - } -} - -export default class APIClient { - static async getLinks() { - return await doRequest(`/links`, zGetLinksResponseSchema, { - next: { tags: ["links"] }, - }); - } - - static async bookmarkLink(url: string) { - const body: ZNewBookmarkedLinkRequest = { - url, - }; - return await doRequest(`/links`, undefined, { - method: "POST", - body: JSON.stringify(body), - }); - } - - static async unbookmarkLink(linkId: string) { - return await doRequest(`/links/${linkId}`, undefined, { - method: "DELETE", - }); - } -} diff --git a/web/lib/auth.ts b/web/lib/auth.ts deleted file mode 100644 index cd6404de..00000000 --- a/web/lib/auth.ts +++ /dev/null @@ -1,25 +0,0 @@ -import NextAuth, { NextAuthOptions } from "next-auth"; -import { PrismaAdapter } from "@next-auth/prisma-adapter"; -import AuthentikProvider from "next-auth/providers/authentik"; -import serverConfig from "@/lib/config"; -import prisma from "@remember/db"; - -let providers = []; - -if (serverConfig.auth.authentik) { - providers.push(AuthentikProvider(serverConfig.auth.authentik)); -} - -export const authOptions: NextAuthOptions = { - // Configure one or more authentication providers - adapter: PrismaAdapter(prisma), - providers: providers, - callbacks: { - session({ session, user }) { - session.user = { ...user }; - return session; - }, - }, -}; - -export const authHandler = NextAuth(authOptions); diff --git a/web/lib/config.ts b/web/lib/config.ts deleted file mode 100644 index ec042b54..00000000 --- a/web/lib/config.ts +++ /dev/null @@ -1,22 +0,0 @@ -function buildAuthentikConfig() { - let { AUTHENTIK_ID, AUTHENTIK_SECRET, AUTHENTIK_ISSUER } = process.env; - - if (!AUTHENTIK_ID || !AUTHENTIK_SECRET || !AUTHENTIK_ISSUER) { - return undefined; - } - - return { - clientId: AUTHENTIK_ID, - clientSecret: AUTHENTIK_SECRET, - issuer: AUTHENTIK_ISSUER, - }; -} - -const serverConfig = { - api_url: process.env.API_URL || "http://localhost:3000", - auth: { - authentik: buildAuthentikConfig(), - }, -}; - -export default serverConfig; diff --git a/web/lib/services/links.ts b/web/lib/services/links.ts deleted file mode 100644 index d273b118..00000000 --- a/web/lib/services/links.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { LinkCrawlerQueue } from "@remember/shared/queues"; -import prisma from "@remember/db"; -import { ZBookmarkedLink } from "@/lib/types/api/links"; - -const defaultLinkFields = { - id: true, - url: true, - createdAt: true, - details: { - select: { - title: true, - description: true, - imageUrl: true, - favicon: true, - }, - }, - tags: { - include: { - tag: true, - }, - }, -}; - -async function dummyPrismaReturnType() { - return await prisma.bookmarkedLink.findFirstOrThrow({ - select: defaultLinkFields, - }); -} - -function toZodSchema( - link: Awaited<ReturnType<typeof dummyPrismaReturnType>>, -): ZBookmarkedLink { - return { - id: link.id, - url: link.url, - createdAt: link.createdAt, - details: link.details, - tags: link.tags.map((t) => t.tag), - }; -} - -export async function unbookmarkLink(linkId: string, userId: string) { - await prisma.bookmarkedLink.delete({ - where: { - id: linkId, - userId, - }, - }); -} - -export async function bookmarkLink(url: string, userId: string) { - const link = await prisma.bookmarkedLink.create({ - data: { - url, - userId, - }, - select: defaultLinkFields, - }); - - // Enqueue crawling request - await LinkCrawlerQueue.add("crawl", { - linkId: link.id, - url: link.url, - }); - - return toZodSchema(link); -} - -export async function getLinks(userId: string) { - return ( - await prisma.bookmarkedLink.findMany({ - where: { - userId, - }, - select: defaultLinkFields, - }) - ).map(toZodSchema); -} diff --git a/web/lib/types/api/links.ts b/web/lib/types/api/links.ts deleted file mode 100644 index f84445f6..00000000 --- a/web/lib/types/api/links.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { z } from "zod"; -import { zBookmarkTagSchema } from "@/lib/types/api/tags"; - -export const zBookmarkedLinkSchema = z.object({ - id: z.string(), - url: z.string().url(), - createdAt: z.coerce.date(), - - details: z - .object({ - title: z.string().nullish(), - description: z.string().nullish(), - imageUrl: z.string().url().nullish(), - favicon: z.string().url().nullish(), - }) - .nullish(), - tags: z.array(zBookmarkTagSchema), -}); -export type ZBookmarkedLink = z.infer<typeof zBookmarkedLinkSchema>; - -// POST /v1/links -export const zNewBookmarkedLinkRequestSchema = zBookmarkedLinkSchema.pick({ - url: true, -}); -export type ZNewBookmarkedLinkRequest = z.infer< - typeof zNewBookmarkedLinkRequestSchema ->; - -// GET /v1/links -export const zGetLinksResponseSchema = z.object({ - links: z.array(zBookmarkedLinkSchema), -}); -export type ZGetLinksResponse = z.infer<typeof zGetLinksResponseSchema>; diff --git a/web/lib/types/api/tags.ts b/web/lib/types/api/tags.ts deleted file mode 100644 index f2d2bc18..00000000 --- a/web/lib/types/api/tags.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { z } from "zod"; - -export const zBookmarkTagSchema = z.object({ - id: z.string(), - name: z.string(), -}); diff --git a/web/lib/types/next-auth.d.ts b/web/lib/types/next-auth.d.ts deleted file mode 100644 index cd47dfce..00000000 --- a/web/lib/types/next-auth.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DefaultSession } from "next-auth"; - -declare module "next-auth" { - /** - * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context - */ - export interface Session { - user: { - id: string; - } & DefaultSession["user"]; - } -} diff --git a/web/lib/utils.ts b/web/lib/utils.ts deleted file mode 100644 index 365058ce..00000000 --- a/web/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/web/next.config.mjs b/web/next.config.mjs deleted file mode 100644 index 4678774e..00000000 --- a/web/next.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {}; - -export default nextConfig; diff --git a/web/package.json b/web/package.json deleted file mode 100644 index 8a315beb..00000000 --- a/web/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/package.json", - "name": "@remember/web", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@hookform/resolvers": "^3.3.4", - "@next-auth/prisma-adapter": "^1.0.7", - "@next/eslint-plugin-next": "^14.1.0", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-toast": "^1.1.5", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "install": "^0.13.0", - "lucide-react": "^0.322.0", - "next": "14.1.0", - "next-auth": "^4.24.5", - "prettier": "^3.2.5", - "react": "^18", - "react-dom": "^18", - "react-hook-form": "^7.50.1", - "tailwind-merge": "^2.2.1", - "tailwindcss-animate": "^1.0.7", - "zod": "^3.22.4" - }, - "devDependencies": { - "@types/react": "^18", - "@types/react-dom": "^18", - "autoprefixer": "^10.0.1", - "postcss": "^8", - "tailwindcss": "^3.3.0" - } -} diff --git a/web/postcss.config.js b/web/postcss.config.js deleted file mode 100644 index 12a703d9..00000000 --- a/web/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/web/public/next.svg b/web/public/next.svg deleted file mode 100644 index 5174b28c..00000000 --- a/web/public/next.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file diff --git a/web/public/vercel.svg b/web/public/vercel.svg deleted file mode 100644 index d2f84222..00000000 --- a/web/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
\ No newline at end of file diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts deleted file mode 100644 index 41668a3b..00000000 --- a/web/tailwind.config.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Config } from "tailwindcss"; - -const config = { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} satisfies Config; - -export default config; diff --git a/web/tsconfig.json b/web/tsconfig.json deleted file mode 100644 index a25dbc14..00000000 --- a/web/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} |
