├── .gitignore
├── .prettierrc
├── README.md
├── app
├── components
│ ├── left-sidebar.tsx
│ ├── menu.tsx
│ ├── right-sidebar.tsx
│ ├── search.tsx
│ ├── thread-actions.tsx
│ ├── thread-list.tsx
│ └── welcome-toast.tsx
├── f
│ └── [name]
│ │ ├── [id]
│ │ ├── loading.tsx
│ │ └── page.tsx
│ │ ├── new
│ │ └── page.tsx
│ │ └── page.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
└── search
│ └── page.tsx
├── components.json
├── components
└── ui
│ ├── alert.tsx
│ ├── button.tsx
│ ├── sheet.tsx
│ └── tooltip.tsx
├── drizzle.config.ts
├── lib
├── db
│ ├── actions.ts
│ ├── drizzle.ts
│ ├── migrate.ts
│ ├── migrations
│ │ ├── 0000_warm_warbird.sql
│ │ └── meta
│ │ │ ├── 0000_snapshot.json
│ │ │ └── _journal.json
│ ├── queries.ts
│ ├── schema.ts
│ ├── seed.ts
│ └── setup.ts
└── utils.tsx
├── next.config.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── public
├── github.svg
├── linkedin.svg
├── placeholder.svg
└── x.svg
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # env files
29 | .env*
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 | .vscode
38 | .idea
39 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "prettier-plugin-organize-imports",
4 | "prettier-plugin-tailwindcss"
5 | ],
6 | "semi": true,
7 | "singleQuote": true,
8 | "tailwindStylesheet": "./app/globals.css",
9 | "tailwindFunctions": ["cn", "clsx", "cva"]
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js Email Client
2 |
3 | This is an email client template built with Next.js and Postgres. It's built to show off some of the features of the App Router, which enable you to build products that:
4 |
5 | - Navigate between routes in a column layout while maintaining scroll position (layouts support)
6 | - Submit forms without JavaScript enabled (progressive enhancement)
7 | - Navigate between routes extremely fast (prefetching and caching)
8 | - Retain your UI position on reload (URL state)
9 |
10 | **Demo: https://next-email-client.vercel.app**
11 |
12 | ## Tech Stack
13 |
14 | - **Framework**: [Next.js](https://nextjs.org/)
15 | - **Database**: [Postgres](https://www.postgresql.org/)
16 | - **ORM**: [Drizzle](https://orm.drizzle.team/)
17 | - **Styling**: [Tailwind CSS](https://tailwindcss.com/)
18 | - **UI Library**: [shadcn/ui](https://ui.shadcn.com/)
19 |
20 | ## Getting Started
21 |
22 | ```bash
23 | git clone https://github.com/leerob/next-email-client
24 | cd next-email-client
25 | pnpm install
26 | ```
27 |
28 | ## Running Locally
29 |
30 | Use the included setup script to create your `.env` file:
31 |
32 | ```bash
33 | pnpm db:setup
34 | ```
35 |
36 | Then, run the database migrations and seed the database with emails and folders:
37 |
38 | ```bash
39 | pnpm db:migrate
40 | pnpm db:seed
41 | ```
42 |
43 | Finally, run the Next.js development server:
44 |
45 | ```bash
46 | pnpm dev
47 | ```
48 |
49 | Open [http://localhost:3000](http://localhost:3000) in your browser to see the app in action.
50 |
51 | ## Implemented
52 |
53 | - ✅ Search for emails
54 | - ✅ Profile sidebar with user information
55 | - ✅ View all threads
56 | - ✅ View all emails in a thread
57 | - ✅ Compose view
58 | - ✅ Seed and setup script
59 | - ✅ Highlight searched text
60 | - ✅ Hook up compose view
61 | - ✅ Delete emails (move to trash)
62 | - Make side profile dynamic
63 | - Support Markdown?
64 | - Make up/down arrows work for threads
65 | - Global keyboard shortcuts
66 | - Better date formatting
67 | - Dark mode styles
68 |
--------------------------------------------------------------------------------
/app/components/left-sidebar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Button } from '@/components/ui/button';
4 | import { ArrowLeft, ChevronDown, ChevronUp } from 'lucide-react';
5 | import Link from 'next/link';
6 | import { useParams } from 'next/navigation';
7 | import { Suspense } from 'react';
8 |
9 | function BackButton() {
10 | let { name } = useParams();
11 |
12 | return (
13 |
14 |
21 |
22 | );
23 | }
24 |
25 | export function LeftSidebar() {
26 | return (
27 |
28 |
29 |
30 |
31 |
38 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/app/components/menu.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Sheet,
3 | SheetContent,
4 | SheetTitle,
5 | SheetTrigger,
6 | } from '@/components/ui/sheet';
7 | import { Check, FileText, Menu, Send, Star, Trash } from 'lucide-react';
8 | import Link from 'next/link';
9 |
10 | export function NavMenu() {
11 | return (
12 |
13 |
14 |
17 |
18 |
22 | Menu
23 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/app/components/right-sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { getUserProfile } from '@/lib/db/queries';
2 | import Image from 'next/image';
3 |
4 | export async function RightSidebar({ userId }: { userId: number }) {
5 | let user = await getUserProfile(userId);
6 |
7 | if (!user) {
8 | return null;
9 | }
10 |
11 | return (
12 |
13 |
14 |
{`${user.firstName} ${user.lastName}`}
15 |
16 |

21 |
22 |
{user.email}
23 |
{user.location}
24 |
25 |
26 |
{`${user.jobTitle} at ${user.company}`}
27 |
28 |
Mail
29 |
30 | {user.latestThreads.map((thread, index) => (
31 | - {thread.subject}
32 | ))}
33 |
34 |
35 |
88 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/app/components/search.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Form from 'next/form';
4 | import { useSearchParams } from 'next/navigation';
5 | import { useEffect, useRef } from 'react';
6 |
7 | export function Search() {
8 | let inputRef = useRef(null);
9 | let searchParams = useSearchParams();
10 |
11 | useEffect(() => {
12 | if (inputRef.current) {
13 | inputRef.current.focus();
14 | inputRef.current.setSelectionRange(
15 | inputRef.current.value.length,
16 | inputRef.current.value.length,
17 | );
18 | }
19 | }, []);
20 |
21 | return (
22 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/app/components/thread-actions.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | Tooltip,
5 | TooltipContent,
6 | TooltipProvider,
7 | TooltipTrigger,
8 | } from '@/components/ui/tooltip';
9 | import { moveThreadToDone, moveThreadToTrash } from '@/lib/db/actions';
10 | import { Archive, Check, Clock } from 'lucide-react';
11 | import { useActionState } from 'react';
12 |
13 | interface ThreadActionsProps {
14 | threadId: number;
15 | }
16 |
17 | export function ThreadActions({ threadId }: ThreadActionsProps) {
18 | const initialState = {
19 | error: null,
20 | success: false,
21 | };
22 |
23 | const [doneState, doneAction, donePending] = useActionState(
24 | moveThreadToDone,
25 | initialState,
26 | );
27 | const [trashState, trashAction, trashPending] = useActionState(
28 | moveThreadToTrash,
29 | initialState,
30 | );
31 |
32 | const isProduction = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production';
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
49 |
50 | {isProduction && (
51 |
52 | Marking as done is disabled in production
53 |
54 | )}
55 |
56 |
57 |
58 |
64 |
65 |
66 | This feature is not yet implemented
67 |
68 |
69 |
70 |
71 |
81 |
82 | {isProduction && (
83 |
84 | Moving to trash is disabled in production
85 |
86 | )}
87 |
88 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/app/components/thread-list.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { ThreadActions } from '@/app/components/thread-actions';
4 | import { emails, users } from '@/lib/db/schema';
5 | import { formatEmailString } from '@/lib/utils';
6 | import { PenSquare, Search } from 'lucide-react';
7 | import Link from 'next/link';
8 | import { useEffect, useState } from 'react';
9 | import { NavMenu } from './menu';
10 |
11 | type Email = Omit & {
12 | sender: Pick;
13 | };
14 | type User = typeof users.$inferSelect;
15 |
16 | type ThreadWithEmails = {
17 | id: number;
18 | subject: string | null;
19 | lastActivityDate: Date | null;
20 | emails: Email[];
21 | };
22 |
23 | interface ThreadListProps {
24 | folderName: string;
25 | threads: ThreadWithEmails[];
26 | searchQuery?: string;
27 | }
28 |
29 | export function ThreadHeader({
30 | folderName,
31 | count,
32 | }: {
33 | folderName: string;
34 | count?: number | undefined;
35 | }) {
36 | return (
37 |
38 |
39 |
40 |
41 | {folderName}
42 | {count}
43 |
44 |
45 |
46 |
50 |
51 |
52 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | export function ThreadList({ folderName, threads }: ThreadListProps) {
64 | const [hoveredThread, setHoveredThread] = useState(null);
65 | const [isMobile, setIsMobile] = useState(false);
66 |
67 | useEffect(() => {
68 | const checkIsMobile = () => {
69 | setIsMobile(window.matchMedia('(hover: none)').matches);
70 | };
71 |
72 | checkIsMobile();
73 | window.addEventListener('resize', checkIsMobile);
74 |
75 | return () => {
76 | window.removeEventListener('resize', checkIsMobile);
77 | };
78 | }, []);
79 |
80 | const handleMouseEnter = (threadId: number) => {
81 | if (!isMobile) {
82 | setHoveredThread(threadId);
83 | }
84 | };
85 |
86 | const handleMouseLeave = () => {
87 | if (!isMobile) {
88 | setHoveredThread(null);
89 | }
90 | };
91 |
92 | return (
93 |
94 |
95 |
96 | {threads.map((thread) => {
97 | const latestEmail = thread.emails[0];
98 |
99 | return (
100 |
105 |
handleMouseEnter(thread.id)}
108 | onMouseLeave={handleMouseLeave}
109 | >
110 |
111 |
112 |
113 | {formatEmailString(latestEmail.sender)}
114 |
115 |
116 |
117 |
118 | {thread.subject}
119 |
120 |
121 | {latestEmail.body}
122 |
123 |
124 |
125 |
126 | {!isMobile && hoveredThread === thread.id ? (
127 |
128 | ) : (
129 |
130 | {new Date(thread.lastActivityDate!).toLocaleDateString()}
131 |
132 | )}
133 |
134 |
135 |
136 | );
137 | })}
138 |
139 |
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/app/components/welcome-toast.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect } from 'react';
4 | import { toast } from 'sonner';
5 |
6 | export function WelcomeToast() {
7 | useEffect(() => {
8 | if (!document.cookie.includes('email-toast=1')) {
9 | toast('📩 Welcome to Next.js Emails!', {
10 | duration: Infinity,
11 | onDismiss: () =>
12 | (document.cookie = 'email-toast=1; max-age=31536000; path=/'),
13 | description: (
14 |
15 | This is a demo of an email client UI with a Postgres database.{' '}
16 |
21 | Deploy your own
22 |
23 | .
24 |
25 | ),
26 | });
27 | }
28 | }, []);
29 |
30 | return null;
31 | }
32 |
--------------------------------------------------------------------------------
/app/f/[name]/[id]/loading.tsx:
--------------------------------------------------------------------------------
1 | import { LeftSidebar } from '@/app/components/left-sidebar';
2 |
3 | export default function LoadingThreadSkeleton() {
4 | return (
5 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/app/f/[name]/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | import { LeftSidebar } from '@/app/components/left-sidebar';
2 | import { ThreadActions } from '@/app/components/thread-actions';
3 | import { getEmailsForThread } from '@/lib/db/queries';
4 | import { notFound } from 'next/navigation';
5 |
6 | export default async function EmailPage({
7 | params,
8 | }: {
9 | params: Promise<{ name: string; id: string }>;
10 | }) {
11 | let id = (await params).id;
12 | let thread = await getEmailsForThread(id);
13 |
14 | if (!thread || thread.emails.length === 0) {
15 | notFound();
16 | }
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 | {thread.subject}
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 | {thread.emails.map((email) => (
36 |
37 |
38 |
39 | {email.sender.firstName} {email.sender.lastName} to{' '}
40 | {email.recipientId === thread.emails[0].sender.id
41 | ? 'Me'
42 | : 'All'}
43 |
44 |
45 | {new Date(email.sentDate!).toLocaleString()}
46 |
47 |
48 |
{email.body}
49 |
50 | ))}
51 |
52 |
53 |
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/app/f/[name]/new/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { LeftSidebar } from '@/app/components/left-sidebar';
4 | import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
5 | import {
6 | Tooltip,
7 | TooltipContent,
8 | TooltipProvider,
9 | TooltipTrigger,
10 | } from '@/components/ui/tooltip';
11 | import { sendEmailAction } from '@/lib/db/actions';
12 | import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
13 | import { Paperclip, Trash2 } from 'lucide-react';
14 | import Link from 'next/link';
15 | import { useParams } from 'next/navigation';
16 | import { Suspense, useActionState } from 'react';
17 |
18 | function DiscardDraftLink() {
19 | let { name } = useParams();
20 |
21 | return (
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | function EmailBody({ defaultValue = '' }: { defaultValue?: string }) {
29 | const handleKeyDown = (e: React.KeyboardEvent) => {
30 | if (
31 | (e.ctrlKey || e.metaKey) &&
32 | (e.key === 'Enter' || e.key === 'NumpadEnter')
33 | ) {
34 | e.preventDefault();
35 | e.currentTarget.form?.requestSubmit();
36 | }
37 | };
38 |
39 | return (
40 |
41 |
49 |
50 | );
51 | }
52 |
53 | export default function ComposePage() {
54 | let [state, formAction] = useActionState(sendEmailAction, {
55 | error: '',
56 | previous: {
57 | recipientEmail: '',
58 | subject: '',
59 | body: '',
60 | },
61 | });
62 |
63 | const isProduction = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production';
64 |
65 | return (
66 |
67 |
68 |
69 |
New Message
70 | {state.error && (
71 |
72 |
73 |
74 | Error
75 | {state.error}
76 |
77 |
78 | )}
79 |
173 |
174 |
175 | );
176 | }
177 |
--------------------------------------------------------------------------------
/app/f/[name]/page.tsx:
--------------------------------------------------------------------------------
1 | import { ThreadHeader, ThreadList } from '@/app/components/thread-list';
2 | import { getThreadsForFolder } from '@/lib/db/queries';
3 | import { Suspense } from 'react';
4 |
5 | export function generateStaticParams() {
6 | const folderNames = [
7 | 'inbox',
8 | 'starred',
9 | 'drafts',
10 | 'sent',
11 | 'archive',
12 | 'trash',
13 | ];
14 |
15 | return folderNames.map((name) => ({ name }));
16 | }
17 |
18 | export default function ThreadsPage({
19 | params,
20 | searchParams,
21 | }: {
22 | params: Promise<{ name: string }>;
23 | searchParams: Promise<{ q?: string; id?: string }>;
24 | }) {
25 | return (
26 |
27 | }>
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | function ThreadsSkeleton({ folderName }: { folderName: string }) {
35 | return (
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | async function Threads({
43 | params,
44 | searchParams,
45 | }: {
46 | params: Promise<{ name: string }>;
47 | searchParams: Promise<{ q?: string; id?: string }>;
48 | }) {
49 | let { name } = await params;
50 | let { q } = await searchParams;
51 | let threads = await getThreadsForFolder(name);
52 |
53 | return ;
54 | }
55 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leerob/next-email-client/4f3337cff0bee0822b6f6b817c729d8d71955ef8/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 |
3 | @plugin "tailwindcss-animate";
4 |
5 | @custom-variant dark (&:is(.dark *));
6 |
7 | @layer base {
8 | :root {
9 | --background: hsl(0 0% 100%);
10 | --foreground: hsl(222.2 84% 4.9%);
11 | --card: hsl(0 0% 100%);
12 | --card-foreground: hsl(222.2 84% 4.9%);
13 | --popover: hsl(0 0% 100%);
14 | --popover-foreground: hsl(222.2 84% 4.9%);
15 | --primary: hsl(222.2 47.4% 11.2%);
16 | --primary-foreground: hsl(210 40% 98%);
17 | --secondary: hsl(210 40% 96.1%);
18 | --secondary-foreground: hsl(222.2 47.4% 11.2%);
19 | --muted: hsl(210 40% 96.1%);
20 | --muted-foreground: hsl(215.4 16.3% 46.9%);
21 | --accent: hsl(210 40% 96.1%);
22 | --accent-foreground: hsl(222.2 47.4% 11.2%);
23 | --destructive: hsl(0 84.2% 60.2%);
24 | --destructive-foreground: hsl(210 40% 98%);
25 | --border: hsl(214.3 31.8% 91.4%);
26 | --input: hsl(214.3 31.8% 91.4%);
27 | --ring: hsl(222.2 84% 4.9%);
28 | --chart-1: hsl(12 76% 61%);
29 | --chart-2: hsl(173 58% 39%);
30 | --chart-3: hsl(197 37% 24%);
31 | --chart-4: hsl(43 74% 66%);
32 | --chart-5: hsl(27 87% 67%);
33 | --radius: 0.5rem;
34 | }
35 |
36 | .dark {
37 | --background: hsl(222.2 84% 4.9%);
38 | --foreground: hsl(210 40% 98%);
39 | --card: hsl(222.2 84% 4.9%);
40 | --card-foreground: hsl(210 40% 98%);
41 | --popover: hsl(222.2 84% 4.9%);
42 | --popover-foreground: hsl(210 40% 98%);
43 | --primary: hsl(210 40% 98%);
44 | --primary-foreground: hsl(222.2 47.4% 11.2%);
45 | --secondary: hsl(217.2 32.6% 17.5%);
46 | --secondary-foreground: hsl(210 40% 98%);
47 | --muted: hsl(217.2 32.6% 17.5%);
48 | --muted-foreground: hsl(215 20.2% 65.1%);
49 | --accent: hsl(217.2 32.6% 17.5%);
50 | --accent-foreground: hsl(210 40% 98%);
51 | --destructive: hsl(0 62.8% 30.6%);
52 | --destructive-foreground: hsl(210 40% 98%);
53 | --border: hsl(217.2 32.6% 17.5%);
54 | --input: hsl(217.2 32.6% 17.5%);
55 | --ring: hsl(212.7 26.8% 83.9%);
56 | --chart-1: hsl(220 70% 50%);
57 | --chart-2: hsl(160 60% 45%);
58 | --chart-3: hsl(30 80% 55%);
59 | --chart-4: hsl(280 65% 60%);
60 | --chart-5: hsl(340 75% 55%);
61 | }
62 | }
63 |
64 | @theme inline {
65 | --color-background: var(--background);
66 | --color-foreground: var(--foreground);
67 | --color-card: var(--card);
68 | --color-card-foreground: var(--card-foreground);
69 | --color-popover: var(--popover);
70 | --color-popover-foreground: var(--popover-foreground);
71 | --color-primary: var(--primary);
72 | --color-primary-foreground: var(--primary-foreground);
73 | --color-secondary: var(--secondary);
74 | --color-secondary-foreground: var(--secondary-foreground);
75 | --color-muted: var(--muted);
76 | --color-muted-foreground: var(--muted-foreground);
77 | --color-accent: var(--accent);
78 | --color-accent-foreground: var(--accent-foreground);
79 | --color-destructive: var(--destructive);
80 | --color-destructive-foreground: var(--destructive-foreground);
81 | --color-border: var(--border);
82 | --color-input: var(--input);
83 | --color-ring: var(--ring);
84 | --color-chart-1: var(--chart-1);
85 | --color-chart-2: var(--chart-2);
86 | --color-chart-3: var(--chart-3);
87 | --color-chart-4: var(--chart-4);
88 | --color-chart-5: var(--chart-5);
89 | --radius-sm: calc(var(--radius) - 4px);
90 | --radius-md: calc(var(--radius) - 2px);
91 | --radius-lg: var(--radius);
92 | --radius-xl: calc(var(--radius) + 4px);
93 | }
94 |
95 | @layer base {
96 | * {
97 | @apply border-border outline-ring/50;
98 | }
99 |
100 | body {
101 | @apply bg-background text-foreground;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next';
2 | import { Inter } from 'next/font/google';
3 | import { Suspense } from 'react';
4 | import { Toaster } from 'sonner';
5 | import { RightSidebar } from './components/right-sidebar';
6 | import { WelcomeToast } from './components/welcome-toast';
7 | import './globals.css';
8 |
9 | const inter = Inter({ subsets: ['latin'] });
10 |
11 | export const metadata: Metadata = {
12 | title: 'Next.js Mail',
13 | description: 'An email client template using the Next.js App Router.',
14 | };
15 |
16 | export default function RootLayout({
17 | children,
18 | }: {
19 | children: React.ReactNode;
20 | }) {
21 | return (
22 |
23 |
24 | {children}
25 | }>
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | function RightSidebarSkeleton() {
36 | return (
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/app/search/page.tsx:
--------------------------------------------------------------------------------
1 | import { searchThreads } from '@/lib/db/queries';
2 | import { formatEmailString, highlightText } from '@/lib/utils';
3 | import { X } from 'lucide-react';
4 | import Link from 'next/link';
5 | import { Suspense } from 'react';
6 | import { NavMenu } from '../components/menu';
7 | import { Search } from '../components/search';
8 |
9 | async function Threads({
10 | searchParams,
11 | }: {
12 | searchParams: Promise<{ q?: string; id?: string }>;
13 | }) {
14 | let q = (await searchParams).q;
15 | let threads = await searchThreads(q);
16 |
17 | return (
18 |
19 | {threads.map((thread) => {
20 | const latestEmail = thread.latestEmail;
21 | return (
22 |
26 |
29 |
30 |
31 |
32 | {highlightText(formatEmailString(latestEmail.sender), q)}
33 |
34 |
35 |
36 |
37 | {highlightText(thread.subject, q)}
38 |
39 |
40 | {highlightText(latestEmail.body, q)}
41 |
42 |
43 |
44 |
45 |
46 | {new Date(thread.lastActivityDate).toLocaleDateString()}
47 |
48 |
49 |
50 |
51 | );
52 | })}
53 |
54 | );
55 | }
56 |
57 | export default async function SearchPage({
58 | searchParams,
59 | }: {
60 | searchParams: Promise<{ q?: string; id?: string }>;
61 | }) {
62 | return (
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from 'class-variance-authority';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const alertVariants = cva(
7 | 'relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
8 | {
9 | variants: {
10 | variant: {
11 | default: 'bg-background text-foreground',
12 | destructive:
13 | 'text-destructive-foreground *:data-[slot=alert-description]:text-destructive-foreground/80 [&>svg]:text-current',
14 | },
15 | },
16 | defaultVariants: {
17 | variant: 'default',
18 | },
19 | },
20 | );
21 |
22 | function Alert({
23 | className,
24 | variant,
25 | ...props
26 | }: React.ComponentProps<'div'> & VariantProps) {
27 | return (
28 |
34 | );
35 | }
36 |
37 | function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
38 | return (
39 |
47 | );
48 | }
49 |
50 | function AlertDescription({
51 | className,
52 | ...props
53 | }: React.ComponentProps<'div'>) {
54 | return (
55 |
63 | );
64 | }
65 |
66 | export { Alert, AlertDescription, AlertTitle };
67 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from '@radix-ui/react-slot';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 | import * as React from 'react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const buttonVariants = cva(
8 | "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
14 | destructive:
15 | 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
16 | outline:
17 | 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
18 | secondary:
19 | 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
20 | ghost: 'hover:bg-accent hover:text-accent-foreground',
21 | link: 'text-primary underline-offset-4 hover:underline',
22 | },
23 | size: {
24 | default: 'h-9 px-4 py-2 has-[>svg]:px-3',
25 | sm: 'h-8 gap-1.5 rounded-md px-3 text-xs has-[>svg]:px-2.5',
26 | lg: 'h-8 rounded-md px-2 has-[>svg]:px-2 sm:h-10',
27 | icon: 'size-9',
28 | },
29 | },
30 | defaultVariants: {
31 | variant: 'default',
32 | size: 'default',
33 | },
34 | },
35 | );
36 |
37 | function Button({
38 | className,
39 | variant,
40 | size,
41 | asChild = false,
42 | ...props
43 | }: React.ComponentProps<'button'> &
44 | VariantProps & {
45 | asChild?: boolean;
46 | }) {
47 | const Comp = asChild ? Slot : 'button';
48 |
49 | return (
50 |
55 | );
56 | }
57 |
58 | export { Button, buttonVariants };
59 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as SheetPrimitive from '@radix-ui/react-dialog';
4 | import { XIcon } from 'lucide-react';
5 | import * as React from 'react';
6 |
7 | import { Button } from '@/components/ui/button';
8 | import { cn } from '@/lib/utils';
9 |
10 | function Sheet({ ...props }: React.ComponentProps) {
11 | return ;
12 | }
13 |
14 | function SheetTrigger({
15 | ...props
16 | }: React.ComponentProps) {
17 | return ;
18 | }
19 |
20 | function SheetClose({
21 | ...props
22 | }: React.ComponentProps) {
23 | return ;
24 | }
25 |
26 | function SheetPortal({
27 | ...props
28 | }: React.ComponentProps) {
29 | return ;
30 | }
31 |
32 | function SheetOverlay({
33 | className,
34 | ...props
35 | }: React.ComponentProps) {
36 | return (
37 |
45 | );
46 | }
47 |
48 | function SheetContent({
49 | className,
50 | children,
51 | side = 'right',
52 | ...props
53 | }: React.ComponentProps & {
54 | side?: 'top' | 'right' | 'bottom' | 'left';
55 | }) {
56 | return (
57 |
58 |
59 |
75 | {children}
76 |
77 |
85 |
86 |
87 |
88 | );
89 | }
90 |
91 | function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
92 | return (
93 |
98 | );
99 | }
100 |
101 | function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
102 | return (
103 |
108 | );
109 | }
110 |
111 | function SheetTitle({
112 | className,
113 | ...props
114 | }: React.ComponentProps) {
115 | return (
116 |
121 | );
122 | }
123 |
124 | function SheetDescription({
125 | className,
126 | ...props
127 | }: React.ComponentProps) {
128 | return (
129 |
134 | );
135 | }
136 |
137 | export {
138 | Sheet,
139 | SheetClose,
140 | SheetContent,
141 | SheetDescription,
142 | SheetFooter,
143 | SheetHeader,
144 | SheetTitle,
145 | SheetTrigger,
146 | };
147 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as TooltipPrimitive from '@radix-ui/react-tooltip';
4 | import * as React from 'react';
5 |
6 | import { cn } from '@/lib/utils';
7 |
8 | function TooltipProvider({
9 | delayDuration = 0,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | );
19 | }
20 |
21 | function Tooltip({
22 | ...props
23 | }: React.ComponentProps) {
24 | return (
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | function TooltipTrigger({
32 | ...props
33 | }: React.ComponentProps) {
34 | return ;
35 | }
36 |
37 | function TooltipContent({
38 | className,
39 | sideOffset = 0,
40 | children,
41 | ...props
42 | }: React.ComponentProps) {
43 | return (
44 |
45 |
54 | {children}
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
62 |
--------------------------------------------------------------------------------
/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'drizzle-kit';
2 |
3 | export default {
4 | schema: './lib/db/schema.ts',
5 | out: './lib/db/migrations',
6 | dialect: 'postgresql',
7 | dbCredentials: {
8 | url: process.env.POSTGRES_URL!,
9 | },
10 | } satisfies Config;
11 |
--------------------------------------------------------------------------------
/lib/db/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { eq } from 'drizzle-orm';
4 | import { revalidatePath } from 'next/cache';
5 | import { redirect } from 'next/navigation';
6 | import { z } from 'zod';
7 | import { db } from './drizzle';
8 | import { emails, folders, threadFolders, threads, users } from './schema';
9 |
10 | const sendEmailSchema = z.object({
11 | subject: z.string().min(1, 'Subject is required'),
12 | body: z.string().min(1, 'Body is required'),
13 | recipientEmail: z.string().email('Invalid email address'),
14 | });
15 |
16 | export async function sendEmailAction(_: any, formData: FormData) {
17 | let newThread;
18 | let rawFormData = {
19 | subject: formData.get('subject'),
20 | body: formData.get('body'),
21 | recipientEmail: formData.get('recipientEmail'),
22 | };
23 |
24 | if (process.env.VERCEL_ENV === 'production') {
25 | return {
26 | error: 'Only works on localhost for now',
27 | previous: rawFormData,
28 | };
29 | }
30 |
31 | try {
32 | let validatedFields = sendEmailSchema.parse({
33 | subject: formData.get('subject'),
34 | body: formData.get('body'),
35 | recipientEmail: formData.get('recipientEmail'),
36 | });
37 |
38 | let { subject, body, recipientEmail } = validatedFields;
39 |
40 | let [recipient] = await db
41 | .select()
42 | .from(users)
43 | .where(eq(users.email, recipientEmail));
44 |
45 | if (!recipient) {
46 | [recipient] = await db
47 | .insert(users)
48 | .values({ email: recipientEmail })
49 | .returning();
50 | }
51 |
52 | let result = await db
53 | .insert(threads)
54 | .values({
55 | subject,
56 | lastActivityDate: new Date(),
57 | })
58 | .returning();
59 | newThread = result[0];
60 |
61 | await db.insert(emails).values({
62 | threadId: newThread.id,
63 | senderId: 1, // Assuming the current user's ID is 1. Replace this with the actual user ID.
64 | recipientId: recipient.id,
65 | subject,
66 | body,
67 | sentDate: new Date(),
68 | });
69 |
70 | let [sentFolder] = await db
71 | .select()
72 | .from(folders)
73 | .where(eq(folders.name, 'Sent'));
74 |
75 | await db.insert(threadFolders).values({
76 | threadId: newThread.id,
77 | folderId: sentFolder.id,
78 | });
79 | } catch (error) {
80 | if (error instanceof z.ZodError) {
81 | return { error: error.errors[0].message, previous: rawFormData };
82 | }
83 | return {
84 | error: 'Failed to send email. Please try again.',
85 | previous: rawFormData,
86 | };
87 | }
88 |
89 | revalidatePath('/', 'layout');
90 | redirect(`/f/sent/${newThread.id}`);
91 | }
92 |
93 | export async function moveThreadToDone(_: any, formData: FormData) {
94 | if (process.env.VERCEL_ENV === 'production') {
95 | return {
96 | error: 'Only works on localhost for now',
97 | };
98 | }
99 |
100 | let threadId = formData.get('threadId');
101 |
102 | if (!threadId || typeof threadId !== 'string') {
103 | return { error: 'Invalid thread ID', success: false };
104 | }
105 |
106 | try {
107 | let doneFolder = await db.query.folders.findFirst({
108 | where: eq(folders.name, 'Archive'),
109 | });
110 |
111 | if (!doneFolder) {
112 | return { error: 'Done folder not found', success: false };
113 | }
114 |
115 | let parsedThreadId = parseInt(threadId, 10);
116 |
117 | await db
118 | .delete(threadFolders)
119 | .where(eq(threadFolders.threadId, parsedThreadId));
120 |
121 | await db.insert(threadFolders).values({
122 | threadId: parsedThreadId,
123 | folderId: doneFolder.id,
124 | });
125 |
126 | revalidatePath('/f/[name]');
127 | revalidatePath('/f/[name]/[id]');
128 | return { success: true, error: null };
129 | } catch (error) {
130 | console.error('Failed to move thread to Done:', error);
131 | return { success: false, error: 'Failed to move thread to Done' };
132 | }
133 | }
134 |
135 | export async function moveThreadToTrash(_: any, formData: FormData) {
136 | if (process.env.VERCEL_ENV === 'production') {
137 | return {
138 | error: 'Only works on localhost for now',
139 | };
140 | }
141 |
142 | let threadId = formData.get('threadId');
143 |
144 | if (!threadId || typeof threadId !== 'string') {
145 | return { error: 'Invalid thread ID', success: false };
146 | }
147 |
148 | try {
149 | let trashFolder = await db.query.folders.findFirst({
150 | where: eq(folders.name, 'Trash'),
151 | });
152 |
153 | if (!trashFolder) {
154 | return { error: 'Trash folder not found', success: false };
155 | }
156 |
157 | let parsedThreadId = parseInt(threadId, 10);
158 |
159 | await db
160 | .delete(threadFolders)
161 | .where(eq(threadFolders.threadId, parsedThreadId));
162 |
163 | await db.insert(threadFolders).values({
164 | threadId: parsedThreadId,
165 | folderId: trashFolder.id,
166 | });
167 |
168 | revalidatePath('/f/[name]');
169 | revalidatePath('/f/[name]/[id]');
170 | return { success: true, error: null };
171 | } catch (error) {
172 | console.error('Failed to move thread to Trash:', error);
173 | return { success: false, error: 'Failed to move thread to Trash' };
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/lib/db/drizzle.ts:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 | import { drizzle } from 'drizzle-orm/postgres-js';
3 | import postgres from 'postgres';
4 | import * as schema from './schema';
5 |
6 | dotenv.config();
7 |
8 | if (!process.env.POSTGRES_URL) {
9 | throw new Error('POSTGRES_URL environment variable is not set');
10 | }
11 |
12 | export const client = postgres(process.env.POSTGRES_URL);
13 | export const db = drizzle(client, { schema });
14 |
--------------------------------------------------------------------------------
/lib/db/migrate.ts:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 | import { migrate } from 'drizzle-orm/postgres-js/migrator';
3 | import path from 'path';
4 | import { client, db } from './drizzle';
5 |
6 | dotenv.config();
7 |
8 | async function main() {
9 | await migrate(db, {
10 | migrationsFolder: path.join(process.cwd(), '/lib/db/migrations'),
11 | });
12 | console.log(`Migrations complete`);
13 | await client.end();
14 | }
15 |
16 | main();
17 |
--------------------------------------------------------------------------------
/lib/db/migrations/0000_warm_warbird.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS "emails" (
2 | "id" serial PRIMARY KEY NOT NULL,
3 | "thread_id" integer,
4 | "sender_id" integer,
5 | "recipient_id" integer,
6 | "subject" varchar(255),
7 | "body" text,
8 | "sent_date" timestamp DEFAULT now()
9 | );
10 | --> statement-breakpoint
11 | CREATE TABLE IF NOT EXISTS "folders" (
12 | "id" serial PRIMARY KEY NOT NULL,
13 | "name" varchar(50) NOT NULL
14 | );
15 | --> statement-breakpoint
16 | CREATE TABLE IF NOT EXISTS "thread_folders" (
17 | "id" serial PRIMARY KEY NOT NULL,
18 | "thread_id" integer,
19 | "folder_id" integer
20 | );
21 | --> statement-breakpoint
22 | CREATE TABLE IF NOT EXISTS "threads" (
23 | "id" serial PRIMARY KEY NOT NULL,
24 | "subject" varchar(255),
25 | "last_activity_date" timestamp DEFAULT now()
26 | );
27 | --> statement-breakpoint
28 | CREATE TABLE IF NOT EXISTS "user_folders" (
29 | "id" serial PRIMARY KEY NOT NULL,
30 | "user_id" integer,
31 | "folder_id" integer
32 | );
33 | --> statement-breakpoint
34 | CREATE TABLE IF NOT EXISTS "users" (
35 | "id" serial PRIMARY KEY NOT NULL,
36 | "first_name" varchar(50),
37 | "last_name" varchar(50),
38 | "email" varchar(255) NOT NULL,
39 | "job_title" varchar(100),
40 | "company" varchar(100),
41 | "location" varchar(100),
42 | "twitter" varchar(100),
43 | "linkedin" varchar(100),
44 | "github" varchar(100),
45 | "avatar_url" varchar(255)
46 | );
47 | --> statement-breakpoint
48 | DO $$ BEGIN
49 | ALTER TABLE "emails" ADD CONSTRAINT "emails_thread_id_threads_id_fk" FOREIGN KEY ("thread_id") REFERENCES "public"."threads"("id") ON DELETE no action ON UPDATE no action;
50 | EXCEPTION
51 | WHEN duplicate_object THEN null;
52 | END $$;
53 | --> statement-breakpoint
54 | DO $$ BEGIN
55 | ALTER TABLE "emails" ADD CONSTRAINT "emails_sender_id_users_id_fk" FOREIGN KEY ("sender_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
56 | EXCEPTION
57 | WHEN duplicate_object THEN null;
58 | END $$;
59 | --> statement-breakpoint
60 | DO $$ BEGIN
61 | ALTER TABLE "emails" ADD CONSTRAINT "emails_recipient_id_users_id_fk" FOREIGN KEY ("recipient_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
62 | EXCEPTION
63 | WHEN duplicate_object THEN null;
64 | END $$;
65 | --> statement-breakpoint
66 | DO $$ BEGIN
67 | ALTER TABLE "thread_folders" ADD CONSTRAINT "thread_folders_thread_id_threads_id_fk" FOREIGN KEY ("thread_id") REFERENCES "public"."threads"("id") ON DELETE no action ON UPDATE no action;
68 | EXCEPTION
69 | WHEN duplicate_object THEN null;
70 | END $$;
71 | --> statement-breakpoint
72 | DO $$ BEGIN
73 | ALTER TABLE "thread_folders" ADD CONSTRAINT "thread_folders_folder_id_folders_id_fk" FOREIGN KEY ("folder_id") REFERENCES "public"."folders"("id") ON DELETE no action ON UPDATE no action;
74 | EXCEPTION
75 | WHEN duplicate_object THEN null;
76 | END $$;
77 | --> statement-breakpoint
78 | DO $$ BEGIN
79 | ALTER TABLE "user_folders" ADD CONSTRAINT "user_folders_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
80 | EXCEPTION
81 | WHEN duplicate_object THEN null;
82 | END $$;
83 | --> statement-breakpoint
84 | DO $$ BEGIN
85 | ALTER TABLE "user_folders" ADD CONSTRAINT "user_folders_folder_id_folders_id_fk" FOREIGN KEY ("folder_id") REFERENCES "public"."folders"("id") ON DELETE no action ON UPDATE no action;
86 | EXCEPTION
87 | WHEN duplicate_object THEN null;
88 | END $$;
89 | --> statement-breakpoint
90 | CREATE INDEX IF NOT EXISTS "thread_id_idx" ON "emails" USING btree ("thread_id");--> statement-breakpoint
91 | CREATE INDEX IF NOT EXISTS "sender_id_idx" ON "emails" USING btree ("sender_id");--> statement-breakpoint
92 | CREATE INDEX IF NOT EXISTS "recipient_id_idx" ON "emails" USING btree ("recipient_id");--> statement-breakpoint
93 | CREATE INDEX IF NOT EXISTS "sent_date_idx" ON "emails" USING btree ("sent_date");--> statement-breakpoint
94 | CREATE UNIQUE INDEX IF NOT EXISTS "email_idx" ON "users" USING btree ("email");
--------------------------------------------------------------------------------
/lib/db/migrations/meta/0000_snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "2bb4aa26-21bc-47db-a17f-dd39c8e3048e",
3 | "prevId": "00000000-0000-0000-0000-000000000000",
4 | "version": "7",
5 | "dialect": "postgresql",
6 | "tables": {
7 | "public.emails": {
8 | "name": "emails",
9 | "schema": "",
10 | "columns": {
11 | "id": {
12 | "name": "id",
13 | "type": "serial",
14 | "primaryKey": true,
15 | "notNull": true
16 | },
17 | "thread_id": {
18 | "name": "thread_id",
19 | "type": "integer",
20 | "primaryKey": false,
21 | "notNull": false
22 | },
23 | "sender_id": {
24 | "name": "sender_id",
25 | "type": "integer",
26 | "primaryKey": false,
27 | "notNull": false
28 | },
29 | "recipient_id": {
30 | "name": "recipient_id",
31 | "type": "integer",
32 | "primaryKey": false,
33 | "notNull": false
34 | },
35 | "subject": {
36 | "name": "subject",
37 | "type": "varchar(255)",
38 | "primaryKey": false,
39 | "notNull": false
40 | },
41 | "body": {
42 | "name": "body",
43 | "type": "text",
44 | "primaryKey": false,
45 | "notNull": false
46 | },
47 | "sent_date": {
48 | "name": "sent_date",
49 | "type": "timestamp",
50 | "primaryKey": false,
51 | "notNull": false,
52 | "default": "now()"
53 | }
54 | },
55 | "indexes": {
56 | "thread_id_idx": {
57 | "name": "thread_id_idx",
58 | "columns": [
59 | {
60 | "expression": "thread_id",
61 | "isExpression": false,
62 | "asc": true,
63 | "nulls": "last"
64 | }
65 | ],
66 | "isUnique": false,
67 | "concurrently": false,
68 | "method": "btree",
69 | "with": {}
70 | },
71 | "sender_id_idx": {
72 | "name": "sender_id_idx",
73 | "columns": [
74 | {
75 | "expression": "sender_id",
76 | "isExpression": false,
77 | "asc": true,
78 | "nulls": "last"
79 | }
80 | ],
81 | "isUnique": false,
82 | "concurrently": false,
83 | "method": "btree",
84 | "with": {}
85 | },
86 | "recipient_id_idx": {
87 | "name": "recipient_id_idx",
88 | "columns": [
89 | {
90 | "expression": "recipient_id",
91 | "isExpression": false,
92 | "asc": true,
93 | "nulls": "last"
94 | }
95 | ],
96 | "isUnique": false,
97 | "concurrently": false,
98 | "method": "btree",
99 | "with": {}
100 | },
101 | "sent_date_idx": {
102 | "name": "sent_date_idx",
103 | "columns": [
104 | {
105 | "expression": "sent_date",
106 | "isExpression": false,
107 | "asc": true,
108 | "nulls": "last"
109 | }
110 | ],
111 | "isUnique": false,
112 | "concurrently": false,
113 | "method": "btree",
114 | "with": {}
115 | }
116 | },
117 | "foreignKeys": {
118 | "emails_thread_id_threads_id_fk": {
119 | "name": "emails_thread_id_threads_id_fk",
120 | "tableFrom": "emails",
121 | "tableTo": "threads",
122 | "columnsFrom": ["thread_id"],
123 | "columnsTo": ["id"],
124 | "onDelete": "no action",
125 | "onUpdate": "no action"
126 | },
127 | "emails_sender_id_users_id_fk": {
128 | "name": "emails_sender_id_users_id_fk",
129 | "tableFrom": "emails",
130 | "tableTo": "users",
131 | "columnsFrom": ["sender_id"],
132 | "columnsTo": ["id"],
133 | "onDelete": "no action",
134 | "onUpdate": "no action"
135 | },
136 | "emails_recipient_id_users_id_fk": {
137 | "name": "emails_recipient_id_users_id_fk",
138 | "tableFrom": "emails",
139 | "tableTo": "users",
140 | "columnsFrom": ["recipient_id"],
141 | "columnsTo": ["id"],
142 | "onDelete": "no action",
143 | "onUpdate": "no action"
144 | }
145 | },
146 | "compositePrimaryKeys": {},
147 | "uniqueConstraints": {}
148 | },
149 | "public.folders": {
150 | "name": "folders",
151 | "schema": "",
152 | "columns": {
153 | "id": {
154 | "name": "id",
155 | "type": "serial",
156 | "primaryKey": true,
157 | "notNull": true
158 | },
159 | "name": {
160 | "name": "name",
161 | "type": "varchar(50)",
162 | "primaryKey": false,
163 | "notNull": true
164 | }
165 | },
166 | "indexes": {},
167 | "foreignKeys": {},
168 | "compositePrimaryKeys": {},
169 | "uniqueConstraints": {}
170 | },
171 | "public.thread_folders": {
172 | "name": "thread_folders",
173 | "schema": "",
174 | "columns": {
175 | "id": {
176 | "name": "id",
177 | "type": "serial",
178 | "primaryKey": true,
179 | "notNull": true
180 | },
181 | "thread_id": {
182 | "name": "thread_id",
183 | "type": "integer",
184 | "primaryKey": false,
185 | "notNull": false
186 | },
187 | "folder_id": {
188 | "name": "folder_id",
189 | "type": "integer",
190 | "primaryKey": false,
191 | "notNull": false
192 | }
193 | },
194 | "indexes": {},
195 | "foreignKeys": {
196 | "thread_folders_thread_id_threads_id_fk": {
197 | "name": "thread_folders_thread_id_threads_id_fk",
198 | "tableFrom": "thread_folders",
199 | "tableTo": "threads",
200 | "columnsFrom": ["thread_id"],
201 | "columnsTo": ["id"],
202 | "onDelete": "no action",
203 | "onUpdate": "no action"
204 | },
205 | "thread_folders_folder_id_folders_id_fk": {
206 | "name": "thread_folders_folder_id_folders_id_fk",
207 | "tableFrom": "thread_folders",
208 | "tableTo": "folders",
209 | "columnsFrom": ["folder_id"],
210 | "columnsTo": ["id"],
211 | "onDelete": "no action",
212 | "onUpdate": "no action"
213 | }
214 | },
215 | "compositePrimaryKeys": {},
216 | "uniqueConstraints": {}
217 | },
218 | "public.threads": {
219 | "name": "threads",
220 | "schema": "",
221 | "columns": {
222 | "id": {
223 | "name": "id",
224 | "type": "serial",
225 | "primaryKey": true,
226 | "notNull": true
227 | },
228 | "subject": {
229 | "name": "subject",
230 | "type": "varchar(255)",
231 | "primaryKey": false,
232 | "notNull": false
233 | },
234 | "last_activity_date": {
235 | "name": "last_activity_date",
236 | "type": "timestamp",
237 | "primaryKey": false,
238 | "notNull": false,
239 | "default": "now()"
240 | }
241 | },
242 | "indexes": {},
243 | "foreignKeys": {},
244 | "compositePrimaryKeys": {},
245 | "uniqueConstraints": {}
246 | },
247 | "public.user_folders": {
248 | "name": "user_folders",
249 | "schema": "",
250 | "columns": {
251 | "id": {
252 | "name": "id",
253 | "type": "serial",
254 | "primaryKey": true,
255 | "notNull": true
256 | },
257 | "user_id": {
258 | "name": "user_id",
259 | "type": "integer",
260 | "primaryKey": false,
261 | "notNull": false
262 | },
263 | "folder_id": {
264 | "name": "folder_id",
265 | "type": "integer",
266 | "primaryKey": false,
267 | "notNull": false
268 | }
269 | },
270 | "indexes": {},
271 | "foreignKeys": {
272 | "user_folders_user_id_users_id_fk": {
273 | "name": "user_folders_user_id_users_id_fk",
274 | "tableFrom": "user_folders",
275 | "tableTo": "users",
276 | "columnsFrom": ["user_id"],
277 | "columnsTo": ["id"],
278 | "onDelete": "no action",
279 | "onUpdate": "no action"
280 | },
281 | "user_folders_folder_id_folders_id_fk": {
282 | "name": "user_folders_folder_id_folders_id_fk",
283 | "tableFrom": "user_folders",
284 | "tableTo": "folders",
285 | "columnsFrom": ["folder_id"],
286 | "columnsTo": ["id"],
287 | "onDelete": "no action",
288 | "onUpdate": "no action"
289 | }
290 | },
291 | "compositePrimaryKeys": {},
292 | "uniqueConstraints": {}
293 | },
294 | "public.users": {
295 | "name": "users",
296 | "schema": "",
297 | "columns": {
298 | "id": {
299 | "name": "id",
300 | "type": "serial",
301 | "primaryKey": true,
302 | "notNull": true
303 | },
304 | "first_name": {
305 | "name": "first_name",
306 | "type": "varchar(50)",
307 | "primaryKey": false,
308 | "notNull": false
309 | },
310 | "last_name": {
311 | "name": "last_name",
312 | "type": "varchar(50)",
313 | "primaryKey": false,
314 | "notNull": false
315 | },
316 | "email": {
317 | "name": "email",
318 | "type": "varchar(255)",
319 | "primaryKey": false,
320 | "notNull": true
321 | },
322 | "job_title": {
323 | "name": "job_title",
324 | "type": "varchar(100)",
325 | "primaryKey": false,
326 | "notNull": false
327 | },
328 | "company": {
329 | "name": "company",
330 | "type": "varchar(100)",
331 | "primaryKey": false,
332 | "notNull": false
333 | },
334 | "location": {
335 | "name": "location",
336 | "type": "varchar(100)",
337 | "primaryKey": false,
338 | "notNull": false
339 | },
340 | "twitter": {
341 | "name": "twitter",
342 | "type": "varchar(100)",
343 | "primaryKey": false,
344 | "notNull": false
345 | },
346 | "linkedin": {
347 | "name": "linkedin",
348 | "type": "varchar(100)",
349 | "primaryKey": false,
350 | "notNull": false
351 | },
352 | "github": {
353 | "name": "github",
354 | "type": "varchar(100)",
355 | "primaryKey": false,
356 | "notNull": false
357 | },
358 | "avatar_url": {
359 | "name": "avatar_url",
360 | "type": "varchar(255)",
361 | "primaryKey": false,
362 | "notNull": false
363 | }
364 | },
365 | "indexes": {
366 | "email_idx": {
367 | "name": "email_idx",
368 | "columns": [
369 | {
370 | "expression": "email",
371 | "isExpression": false,
372 | "asc": true,
373 | "nulls": "last"
374 | }
375 | ],
376 | "isUnique": true,
377 | "concurrently": false,
378 | "method": "btree",
379 | "with": {}
380 | }
381 | },
382 | "foreignKeys": {},
383 | "compositePrimaryKeys": {},
384 | "uniqueConstraints": {}
385 | }
386 | },
387 | "enums": {},
388 | "schemas": {},
389 | "sequences": {},
390 | "_meta": {
391 | "columns": {},
392 | "schemas": {},
393 | "tables": {}
394 | }
395 | }
396 |
--------------------------------------------------------------------------------
/lib/db/migrations/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "7",
3 | "dialect": "postgresql",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "7",
8 | "when": 1727742572262,
9 | "tag": "0000_warm_warbird",
10 | "breakpoints": true
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/lib/db/queries.ts:
--------------------------------------------------------------------------------
1 | import { and, desc, eq, ilike, or, sql } from 'drizzle-orm';
2 | import { toTitleCase } from '../utils';
3 | import { db } from './drizzle';
4 | import { emails, folders, threadFolders, threads, users } from './schema';
5 |
6 | type Folder = {
7 | name: string;
8 | thread_count: number;
9 | };
10 |
11 | export async function getFoldersWithThreadCount() {
12 | 'use cache';
13 |
14 | let foldersWithCount = await db
15 | .select({
16 | name: folders.name,
17 | thread_count: sql`count(${threadFolders.threadId})`.as(
18 | 'thread_count',
19 | ),
20 | })
21 | .from(folders)
22 | .leftJoin(threadFolders, eq(folders.id, threadFolders.folderId))
23 | .groupBy(folders.name);
24 |
25 | let specialFoldersOrder = ['Inbox', 'Flagged', 'Sent'];
26 | let specialFolders = specialFoldersOrder
27 | .map((name) => foldersWithCount.find((folder) => folder.name === name))
28 | .filter(Boolean) as Folder[];
29 | let otherFolders = foldersWithCount.filter(
30 | (folder) => !specialFoldersOrder.includes(folder.name),
31 | ) as Folder[];
32 |
33 | return { specialFolders, otherFolders };
34 | }
35 |
36 | export async function getThreadsForFolder(folderName: string) {
37 | 'use cache';
38 |
39 | let originalFolderName = toTitleCase(decodeURIComponent(folderName));
40 |
41 | const threadsWithEmails = await db
42 | .select({
43 | id: threads.id,
44 | subject: threads.subject,
45 | lastActivityDate: threads.lastActivityDate,
46 | emails: sql<
47 | {
48 | id: number;
49 | senderId: number;
50 | recipientId: number;
51 | subject: string;
52 | body: string;
53 | sentDate: Date;
54 | sender: {
55 | id: number;
56 | firstName: string;
57 | lastName: string;
58 | email: string;
59 | };
60 | }[]
61 | >`json_agg(json_build_object(
62 | 'id', ${emails.id},
63 | 'senderId', ${emails.senderId},
64 | 'recipientId', ${emails.recipientId},
65 | 'subject', ${emails.subject},
66 | 'body', ${emails.body},
67 | 'sentDate', ${emails.sentDate},
68 | 'sender', json_build_object(
69 | 'id', ${users.id},
70 | 'firstName', ${users.firstName},
71 | 'lastName', ${users.lastName},
72 | 'email', ${users.email}
73 | )
74 | ) ORDER BY ${emails.sentDate} DESC)`,
75 | })
76 | .from(threads)
77 | .innerJoin(threadFolders, eq(threads.id, threadFolders.threadId))
78 | .innerJoin(folders, eq(threadFolders.folderId, folders.id))
79 | .innerJoin(emails, eq(threads.id, emails.threadId))
80 | .innerJoin(users, eq(emails.senderId, users.id))
81 | .where(eq(folders.name, originalFolderName))
82 | .groupBy(threads.id)
83 | .orderBy(desc(threads.lastActivityDate));
84 |
85 | return threadsWithEmails;
86 | }
87 |
88 | export async function searchThreads(search: string | undefined) {
89 | if (!search) {
90 | return [];
91 | }
92 |
93 | const results = await db
94 | .select({
95 | id: threads.id,
96 | subject: threads.subject,
97 | lastActivityDate: threads.lastActivityDate,
98 | folderName: folders.name,
99 | emailId: emails.id,
100 | emailSubject: emails.subject,
101 | emailBody: emails.body,
102 | emailSentDate: emails.sentDate,
103 | senderFirstName: users.firstName,
104 | senderLastName: users.lastName,
105 | senderEmail: users.email,
106 | })
107 | .from(threads)
108 | .innerJoin(emails, eq(threads.id, emails.threadId))
109 | .innerJoin(users, eq(emails.senderId, users.id))
110 | .leftJoin(threadFolders, eq(threads.id, threadFolders.threadId))
111 | .leftJoin(folders, eq(threadFolders.folderId, folders.id))
112 | .where(
113 | or(
114 | ilike(users.firstName, `%${search}%`),
115 | ilike(users.lastName, `%${search}%`),
116 | ilike(users.email, `%${search}%`),
117 | ilike(threads.subject, `%${search}%`),
118 | ilike(emails.body, `%${search}%`),
119 | ),
120 | )
121 | .orderBy(desc(threads.lastActivityDate), desc(emails.sentDate));
122 |
123 | const threadMap = new Map();
124 | for (const result of results) {
125 | if (!threadMap.has(result.id)) {
126 | threadMap.set(result.id, {
127 | id: result.id,
128 | subject: result.subject,
129 | lastActivityDate: result.lastActivityDate,
130 | folderName: result.folderName,
131 | latestEmail: {
132 | id: result.emailId,
133 | subject: result.emailSubject,
134 | body: result.emailBody,
135 | sentDate: result.emailSentDate,
136 | sender: {
137 | firstName: result.senderFirstName,
138 | lastName: result.senderLastName,
139 | email: result.senderEmail,
140 | },
141 | },
142 | });
143 | }
144 | }
145 |
146 | return Array.from(threadMap.values());
147 | }
148 |
149 | export async function getThreadInFolder(folderName: string, threadId: string) {
150 | 'use cache';
151 |
152 | let originalFolderName = toTitleCase(decodeURIComponent(folderName));
153 | let result = await db
154 | .select({
155 | id: threads.id,
156 | subject: threads.subject,
157 | lastActivityDate: threads.lastActivityDate,
158 | senderFirstName: users.firstName,
159 | senderLastName: users.lastName,
160 | senderEmail: users.email,
161 | })
162 | .from(threads)
163 | .innerJoin(threadFolders, eq(threads.id, threadFolders.threadId))
164 | .innerJoin(folders, eq(threadFolders.folderId, folders.id))
165 | .innerJoin(emails, eq(threads.id, emails.threadId))
166 | .innerJoin(users, eq(emails.senderId, users.id))
167 | .where(
168 | and(
169 | eq(folders.name, originalFolderName),
170 | eq(threads.id, parseInt(threadId)),
171 | ),
172 | );
173 |
174 | return result[0];
175 | }
176 |
177 | export async function getEmailsForThread(threadId: string) {
178 | 'use cache';
179 |
180 | const result = await db
181 | .select({
182 | id: threads.id,
183 | subject: threads.subject,
184 | emailId: emails.id,
185 | body: emails.body,
186 | sentDate: emails.sentDate,
187 | senderId: users.id,
188 | senderFirstName: users.firstName,
189 | senderLastName: users.lastName,
190 | recipientId: emails.recipientId,
191 | })
192 | .from(threads)
193 | .innerJoin(emails, eq(threads.id, emails.threadId))
194 | .innerJoin(users, eq(emails.senderId, users.id))
195 | .where(eq(threads.id, parseInt(threadId, 10)))
196 | .orderBy(emails.sentDate);
197 |
198 | if (result.length === 0) {
199 | return null;
200 | }
201 |
202 | const thread = {
203 | id: result[0].id,
204 | subject: result[0].subject,
205 | emails: result.map((row) => ({
206 | id: row.emailId,
207 | body: row.body,
208 | sentDate: row.sentDate,
209 | sender: {
210 | id: row.senderId,
211 | firstName: row.senderFirstName,
212 | lastName: row.senderLastName,
213 | },
214 | recipientId: row.recipientId,
215 | })),
216 | };
217 |
218 | return thread;
219 | }
220 |
221 | export async function getAllEmailAddresses() {
222 | 'use cache';
223 |
224 | return db
225 | .select({
226 | firstName: users.firstName,
227 | lastName: users.lastName,
228 | email: users.email,
229 | })
230 | .from(users);
231 | }
232 |
233 | export async function getUserProfile(userId: number) {
234 | 'use cache';
235 |
236 | const userInfo = await db
237 | .select({
238 | id: users.id,
239 | firstName: users.firstName,
240 | lastName: users.lastName,
241 | email: users.email,
242 | jobTitle: users.jobTitle,
243 | company: users.company,
244 | location: users.location,
245 | avatarUrl: users.avatarUrl,
246 | linkedin: users.linkedin,
247 | twitter: users.twitter,
248 | github: users.github,
249 | })
250 | .from(users)
251 | .where(eq(users.id, userId))
252 | .limit(1)
253 | .execute();
254 |
255 | if (userInfo.length === 0) {
256 | return null;
257 | }
258 |
259 | const latestThreads = await db
260 | .select({
261 | subject: threads.subject,
262 | })
263 | .from(threads)
264 | .innerJoin(emails, eq(emails.threadId, threads.id))
265 | .where(eq(emails.senderId, userId))
266 | .orderBy(desc(threads.lastActivityDate))
267 | .limit(3)
268 | .execute();
269 |
270 | return {
271 | ...userInfo[0],
272 | latestThreads,
273 | };
274 | }
275 |
--------------------------------------------------------------------------------
/lib/db/schema.ts:
--------------------------------------------------------------------------------
1 | import { relations } from 'drizzle-orm';
2 | import {
3 | index,
4 | integer,
5 | pgTable,
6 | serial,
7 | text,
8 | timestamp,
9 | uniqueIndex,
10 | varchar,
11 | } from 'drizzle-orm/pg-core';
12 |
13 | export const users = pgTable(
14 | 'users',
15 | {
16 | id: serial('id').primaryKey(),
17 | firstName: varchar('first_name', { length: 50 }),
18 | lastName: varchar('last_name', { length: 50 }),
19 | email: varchar('email', { length: 255 }).notNull(),
20 | jobTitle: varchar('job_title', { length: 100 }),
21 | company: varchar('company', { length: 100 }),
22 | location: varchar('location', { length: 100 }),
23 | twitter: varchar('twitter', { length: 100 }),
24 | linkedin: varchar('linkedin', { length: 100 }),
25 | github: varchar('github', { length: 100 }),
26 | avatarUrl: varchar('avatar_url', { length: 255 }),
27 | },
28 | (table) => {
29 | return {
30 | emailIndex: uniqueIndex('email_idx').on(table.email),
31 | };
32 | },
33 | );
34 |
35 | export const threads = pgTable('threads', {
36 | id: serial('id').primaryKey(),
37 | subject: varchar('subject', { length: 255 }),
38 | lastActivityDate: timestamp('last_activity_date').defaultNow(),
39 | });
40 |
41 | export const emails = pgTable(
42 | 'emails',
43 | {
44 | id: serial('id').primaryKey(),
45 | threadId: integer('thread_id').references(() => threads.id),
46 | senderId: integer('sender_id').references(() => users.id),
47 | recipientId: integer('recipient_id').references(() => users.id),
48 | subject: varchar('subject', { length: 255 }),
49 | body: text('body'),
50 | sentDate: timestamp('sent_date').defaultNow(),
51 | },
52 | (table) => {
53 | return {
54 | threadIdIndex: index('thread_id_idx').on(table.threadId),
55 | senderIdIndex: index('sender_id_idx').on(table.senderId),
56 | recipientIdIndex: index('recipient_id_idx').on(table.recipientId),
57 | sentDateIndex: index('sent_date_idx').on(table.sentDate),
58 | };
59 | },
60 | );
61 |
62 | export const folders = pgTable('folders', {
63 | id: serial('id').primaryKey(),
64 | name: varchar('name', { length: 50 }).notNull(),
65 | });
66 |
67 | export const userFolders = pgTable('user_folders', {
68 | id: serial('id').primaryKey(),
69 | userId: integer('user_id').references(() => users.id),
70 | folderId: integer('folder_id').references(() => folders.id),
71 | });
72 |
73 | export const threadFolders = pgTable('thread_folders', {
74 | id: serial('id').primaryKey(),
75 | threadId: integer('thread_id').references(() => threads.id),
76 | folderId: integer('folder_id').references(() => folders.id),
77 | });
78 |
79 | export const usersRelations = relations(users, ({ many }) => ({
80 | sentEmails: many(emails, { relationName: 'sender' }),
81 | receivedEmails: many(emails, { relationName: 'recipient' }),
82 | userFolders: many(userFolders),
83 | }));
84 |
85 | export const threadsRelations = relations(threads, ({ many }) => ({
86 | emails: many(emails),
87 | threadFolders: many(threadFolders),
88 | }));
89 |
90 | export const emailsRelations = relations(emails, ({ one }) => ({
91 | thread: one(threads, {
92 | fields: [emails.threadId],
93 | references: [threads.id],
94 | }),
95 | sender: one(users, {
96 | fields: [emails.senderId],
97 | references: [users.id],
98 | relationName: 'sender',
99 | }),
100 | recipient: one(users, {
101 | fields: [emails.recipientId],
102 | references: [users.id],
103 | relationName: 'recipient',
104 | }),
105 | }));
106 |
107 | export const foldersRelations = relations(folders, ({ many }) => ({
108 | userFolders: many(userFolders),
109 | threadFolders: many(threadFolders),
110 | }));
111 |
112 | export const userFoldersRelations = relations(userFolders, ({ one }) => ({
113 | user: one(users, { fields: [userFolders.userId], references: [users.id] }),
114 | folder: one(folders, {
115 | fields: [userFolders.folderId],
116 | references: [folders.id],
117 | }),
118 | }));
119 |
120 | export const threadFoldersRelations = relations(threadFolders, ({ one }) => ({
121 | thread: one(threads, {
122 | fields: [threadFolders.threadId],
123 | references: [threads.id],
124 | }),
125 | folder: one(folders, {
126 | fields: [threadFolders.folderId],
127 | references: [folders.id],
128 | }),
129 | }));
130 |
--------------------------------------------------------------------------------
/lib/db/seed.ts:
--------------------------------------------------------------------------------
1 | import { db } from './drizzle';
2 | import {
3 | emails,
4 | folders,
5 | threadFolders,
6 | threads,
7 | userFolders,
8 | users,
9 | } from './schema';
10 |
11 | async function seed() {
12 | console.log('Starting seed process...');
13 | await seedUsers();
14 | await seedFolders();
15 | await seedThreadsAndEmails();
16 | console.log('Seed process completed successfully.');
17 | }
18 |
19 | async function seedUsers() {
20 | await db.insert(users).values([
21 | {
22 | firstName: 'Lee',
23 | lastName: 'Robinson',
24 | email: 'lee@leerob.com',
25 | jobTitle: 'VP of Product',
26 | company: 'Vercel',
27 | location: 'Des Moines, Iowa',
28 | avatarUrl: 'https://github.com/leerob.png',
29 | linkedin: 'https://www.linkedin.com/in/leeerob/',
30 | twitter: 'https://x.com/leerob',
31 | github: 'https://github.com/leerob',
32 | },
33 | {
34 | firstName: 'Guillermo',
35 | lastName: 'Rauch',
36 | email: 'rauchg@vercel.com',
37 | jobTitle: 'CEO',
38 | company: 'Vercel',
39 | location: 'San Francisco, California',
40 | avatarUrl: 'https://github.com/rauchg.png',
41 | },
42 | {
43 | firstName: 'Delba',
44 | lastName: 'de Oliveira',
45 | email: 'delba.oliveira@vercel.com',
46 | jobTitle: 'Staff DX Engineer',
47 | company: 'Vercel',
48 | location: 'London, UK',
49 | avatarUrl: 'https://github.com/delbaoliveira.png',
50 | },
51 | {
52 | firstName: 'Tim',
53 | lastName: 'Neutkens',
54 | email: 'tim@vercel.com',
55 | jobTitle: 'Next.js Lead',
56 | company: 'Vercel',
57 | location: 'Amsterdam, Netherlands',
58 | avatarUrl: 'https://github.com/timneutkens.png',
59 | },
60 | ]);
61 | }
62 |
63 | async function seedFolders() {
64 | await db
65 | .insert(folders)
66 | .values([
67 | { name: 'Inbox' },
68 | { name: 'Flagged' },
69 | { name: 'Sent' },
70 | { name: 'Archive' },
71 | { name: 'Spam' },
72 | { name: 'Trash' },
73 | ]);
74 |
75 | const userFolderValues = [];
76 | for (let userId = 1; userId <= 4; userId++) {
77 | for (let folderId = 1; folderId <= 6; folderId++) {
78 | userFolderValues.push({ userId, folderId });
79 | }
80 | }
81 | await db.insert(userFolders).values(userFolderValues);
82 | }
83 |
84 | async function seedThreadsAndEmails() {
85 | // Thread 1: Guillermo talking about Vercel customer feedback
86 | const thread1 = await db
87 | .insert(threads)
88 | .values({
89 | subject: 'Vercel Customer Feedback',
90 | lastActivityDate: new Date('2023-05-15T10:00:00'),
91 | })
92 | .returning();
93 |
94 | await db.insert(emails).values([
95 | {
96 | threadId: thread1[0].id,
97 | senderId: 2, // Guillermo
98 | recipientId: 1, // Lee
99 | subject: 'Vercel Customer Feedback',
100 | body: 'Met with Daniel today. He had some great feedback. After you make a change to your environment variables, he wants to immediately redeploy the application. We should make a toast that has a CTA to redeploy. Thoughts?',
101 | sentDate: new Date('2023-05-15T10:00:00'),
102 | },
103 | {
104 | threadId: thread1[0].id,
105 | senderId: 1, // Lee
106 | recipientId: 2, // Guillermo
107 | subject: 'Re: Vercel Customer Feedback',
108 | body: "Good call. I've seen this multiple times now. Let's do it.",
109 | sentDate: new Date('2023-05-15T11:30:00'),
110 | },
111 | {
112 | threadId: thread1[0].id,
113 | senderId: 2, // Guillermo
114 | recipientId: 1, // Lee
115 | subject: 'Re: Vercel Customer Feedback',
116 | body: "Amazing. Let me know when it shipped and I'll follow up.",
117 | sentDate: new Date('2023-05-15T13:45:00'),
118 | },
119 | ]);
120 |
121 | // Thread 2: Delba talking about Next.js and testing out new features
122 | const thread2 = await db
123 | .insert(threads)
124 | .values({
125 | subject: 'New Next.js RFC',
126 | lastActivityDate: new Date('2023-05-16T09:00:00'),
127 | })
128 | .returning();
129 |
130 | await db.insert(emails).values([
131 | {
132 | threadId: thread2[0].id,
133 | senderId: 3, // Delba
134 | recipientId: 1, // Lee
135 | subject: 'New Next.js RFC',
136 | body: "I'm working on the first draft of the Dynamic IO docs and examples. Do you want to take a look?",
137 | sentDate: new Date('2023-05-16T09:00:00'),
138 | },
139 | {
140 | threadId: thread2[0].id,
141 | senderId: 1, // Lee
142 | recipientId: 3, // Delba
143 | subject: 'Re: New Next.js RFC',
144 | body: "Absolutely. Let me take a look later tonight and I'll send over feedback.",
145 | sentDate: new Date('2023-05-16T10:15:00'),
146 | },
147 | {
148 | threadId: thread2[0].id,
149 | senderId: 3, // Delba
150 | recipientId: 1, // Lee
151 | subject: 'Re: New Next.js RFC',
152 | body: 'Thank you!',
153 | sentDate: new Date('2023-05-16T11:30:00'),
154 | },
155 | ]);
156 |
157 | // Thread 3: Tim with steps to test out Turbopack
158 | const thread3 = await db
159 | .insert(threads)
160 | .values({
161 | subject: 'Turbopack Testing',
162 | lastActivityDate: new Date('2023-05-17T14:00:00'),
163 | })
164 | .returning();
165 |
166 | await db.insert(emails).values([
167 | {
168 | threadId: thread3[0].id,
169 | senderId: 4, // Tim
170 | recipientId: 1, // Lee
171 | subject: 'Turbopack Testing Steps',
172 | body: `Hi Lee,
173 |
174 | Here are the steps to test out Turbopack:
175 |
176 | 1. npx create-next-app@canary
177 | 2. Select Turbopack when prompted
178 | 3. Run 'npm install' to install dependencies
179 | 4. Start the development server with 'npm run dev -- --turbo'
180 | 5. That's it!
181 |
182 | Let me know if you encounter any issues or have any questions.
183 |
184 | Best,
185 | Tim`,
186 | sentDate: new Date('2023-05-17T14:00:00'),
187 | },
188 | ]);
189 |
190 | // Add threads to folders
191 | await db.insert(threadFolders).values([
192 | { threadId: thread1[0].id, folderId: 1 }, // Inbox
193 | { threadId: thread2[0].id, folderId: 1 }, // Inbox
194 | { threadId: thread3[0].id, folderId: 1 }, // Inbox
195 | ]);
196 | }
197 |
198 | seed()
199 | .catch((error) => {
200 | console.error('Seed process failed:', error);
201 | process.exit(1);
202 | })
203 | .finally(async () => {
204 | console.log('Seed process finished. Exiting...');
205 | process.exit(0);
206 | });
207 |
--------------------------------------------------------------------------------
/lib/db/setup.ts:
--------------------------------------------------------------------------------
1 | import { exec } from 'node:child_process';
2 | import { promises as fs } from 'node:fs';
3 | import path from 'node:path';
4 | import readline from 'node:readline';
5 | import { promisify } from 'node:util';
6 |
7 | const execAsync = promisify(exec);
8 |
9 | function question(query: string): Promise {
10 | const rl = readline.createInterface({
11 | input: process.stdin,
12 | output: process.stdout,
13 | });
14 |
15 | return new Promise((resolve) =>
16 | rl.question(query, (ans) => {
17 | rl.close();
18 | resolve(ans);
19 | }),
20 | );
21 | }
22 |
23 | async function getPostgresURL(): Promise {
24 | console.log('Step 1: Setting up Postgres');
25 | const dbChoice = await question(
26 | 'Do you want to use a local Postgres instance with Docker (L) or a remote Postgres instance (R)? (L/R): ',
27 | );
28 |
29 | if (dbChoice.toLowerCase() === 'l') {
30 | console.log('Setting up local Postgres instance with Docker...');
31 | await setupLocalPostgres();
32 | return 'postgres://postgres:postgres@localhost:54322/postgres';
33 | } else {
34 | console.log(
35 | 'You can find Postgres databases at: https://vercel.com/marketplace?category=databases',
36 | );
37 | return await question('Enter your POSTGRES_URL: ');
38 | }
39 | }
40 |
41 | async function setupLocalPostgres() {
42 | console.log('Checking if Docker is installed...');
43 | try {
44 | await execAsync('docker --version');
45 | console.log('Docker is installed.');
46 | } catch (error) {
47 | console.error(
48 | 'Docker is not installed. Please install Docker and try again.',
49 | );
50 | console.log(
51 | 'To install Docker, visit: https://docs.docker.com/get-docker/',
52 | );
53 | process.exit(1);
54 | }
55 |
56 | console.log('Creating docker-compose.yml file...');
57 | const dockerComposeContent = `
58 | services:
59 | postgres:
60 | image: postgres:16.4-alpine
61 | container_name: music_player_postgres
62 | environment:
63 | POSTGRES_DB: postgres
64 | POSTGRES_USER: postgres
65 | POSTGRES_PASSWORD: postgres
66 | ports:
67 | - "54322:5432"
68 | volumes:
69 | - postgres_data:/var/lib/postgresql/data
70 |
71 | volumes:
72 | postgres_data:
73 | `;
74 |
75 | await fs.writeFile(
76 | path.join(process.cwd(), 'docker-compose.yml'),
77 | dockerComposeContent,
78 | );
79 | console.log('docker-compose.yml file created.');
80 |
81 | console.log('Starting Docker container with `docker compose up -d`...');
82 | try {
83 | await execAsync('docker compose up -d');
84 | console.log('Docker container started successfully.');
85 | } catch (error) {
86 | console.error(
87 | 'Failed to start Docker container. Please check your Docker installation and try again.',
88 | );
89 | process.exit(1);
90 | }
91 | }
92 |
93 | async function writeEnvFile(envVars: Record) {
94 | console.log('Step 3: Writing environment variables to .env');
95 | const envContent = Object.entries(envVars)
96 | .map(([key, value]) => `${key}=${value}`)
97 | .join('\n');
98 |
99 | await fs.writeFile(path.join(process.cwd(), '.env'), envContent);
100 | console.log('.env file created with the necessary variables.');
101 | }
102 |
103 | async function main() {
104 | const POSTGRES_URL = await getPostgresURL();
105 |
106 | await writeEnvFile({
107 | POSTGRES_URL,
108 | });
109 |
110 | console.log('🎉 Setup completed successfully!');
111 | }
112 |
113 | main().catch(console.error);
114 |
--------------------------------------------------------------------------------
/lib/utils.tsx:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function formatEmailString(
9 | userEmail: {
10 | firstName: string | null;
11 | lastName: string | null;
12 | email: string;
13 | },
14 | opts: { includeFullEmail: boolean } = { includeFullEmail: false },
15 | ) {
16 | if (userEmail.firstName && userEmail.lastName) {
17 | return `${userEmail.firstName} ${userEmail.lastName} ${
18 | opts.includeFullEmail ? `<${userEmail.email}>` : ''
19 | }`;
20 | }
21 | return userEmail.email;
22 | }
23 |
24 | export function toTitleCase(str: string) {
25 | return str.replace(/\w\S*/g, function (txt: string) {
26 | return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase();
27 | });
28 | }
29 |
30 | export function highlightText(text: string, query: string | undefined) {
31 | if (!query) return text;
32 | const parts = text.split(new RegExp(`(${query})`, 'gi'));
33 |
34 | return parts.map((part, i) =>
35 | part.toLowerCase() === query.toLowerCase() ? (
36 |
37 | {part}
38 |
39 | ) : (
40 | part
41 | ),
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next';
2 |
3 | const nextConfig: NextConfig = {
4 | experimental: {
5 | ppr: true,
6 | dynamicIO: true,
7 | serverSourceMaps: true,
8 | },
9 | async redirects() {
10 | return [
11 | {
12 | source: '/',
13 | destination: '/f/inbox',
14 | permanent: false,
15 | },
16 | ];
17 | },
18 | };
19 |
20 | export default nextConfig;
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev --turbo",
5 | "build": "next build",
6 | "start": "next start",
7 | "db:setup": "npx tsx lib/db/setup.ts",
8 | "db:seed": "npx tsx lib/db/seed.ts",
9 | "db:generate": "drizzle-kit generate",
10 | "db:migrate": "npx tsx lib/db/migrate.ts",
11 | "db:studio": "drizzle-kit studio"
12 | },
13 | "dependencies": {
14 | "@radix-ui/react-dialog": "^1.1.6",
15 | "@radix-ui/react-icons": "^1.3.2",
16 | "@radix-ui/react-slot": "^1.1.2",
17 | "@radix-ui/react-tooltip": "^1.1.8",
18 | "@tailwindcss/postcss": "^4.0.13",
19 | "@types/node": "^22.10.1",
20 | "@types/react": "19.0.10",
21 | "@types/react-dom": "19.0.4",
22 | "class-variance-authority": "^0.7.1",
23 | "clsx": "^2.1.1",
24 | "dotenv": "^16.4.5",
25 | "drizzle-kit": "^0.28.1",
26 | "drizzle-orm": "^0.36.4",
27 | "geist": "^1.3.1",
28 | "lucide-react": "^0.462.0",
29 | "next": "15.3.0-canary.0",
30 | "postcss": "^8.4.49",
31 | "postgres": "^3.4.5",
32 | "react": "19.0.0",
33 | "react-dom": "19.0.0",
34 | "sonner": "^1.7.0",
35 | "tailwind-merge": "^3.0.2",
36 | "tailwindcss": "^4.0.13",
37 | "tailwindcss-animate": "^1.0.7",
38 | "typescript": "^5.8.2",
39 | "zod": "^3.23.8"
40 | },
41 | "devDependencies": {
42 | "prettier-plugin-organize-imports": "^4.1.0",
43 | "prettier-plugin-tailwindcss": "^0.6.11"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | dependencies:
11 | '@radix-ui/react-dialog':
12 | specifier: ^1.1.6
13 | version: 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
14 | '@radix-ui/react-icons':
15 | specifier: ^1.3.2
16 | version: 1.3.2(react@19.0.0)
17 | '@radix-ui/react-slot':
18 | specifier: ^1.1.2
19 | version: 1.1.2(@types/react@19.0.10)(react@19.0.0)
20 | '@radix-ui/react-tooltip':
21 | specifier: ^1.1.8
22 | version: 1.1.8(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
23 | '@tailwindcss/postcss':
24 | specifier: ^4.0.13
25 | version: 4.0.13
26 | '@types/node':
27 | specifier: ^22.10.1
28 | version: 22.10.1
29 | '@types/react':
30 | specifier: 19.0.10
31 | version: 19.0.10
32 | '@types/react-dom':
33 | specifier: 19.0.4
34 | version: 19.0.4(@types/react@19.0.10)
35 | class-variance-authority:
36 | specifier: ^0.7.1
37 | version: 0.7.1
38 | clsx:
39 | specifier: ^2.1.1
40 | version: 2.1.1
41 | dotenv:
42 | specifier: ^16.4.5
43 | version: 16.4.5
44 | drizzle-kit:
45 | specifier: ^0.28.1
46 | version: 0.28.1
47 | drizzle-orm:
48 | specifier: ^0.36.4
49 | version: 0.36.4(@types/react@19.0.10)(postgres@3.4.5)(react@19.0.0)
50 | geist:
51 | specifier: ^1.3.1
52 | version: 1.3.1(next@15.3.0-canary.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))
53 | lucide-react:
54 | specifier: ^0.462.0
55 | version: 0.462.0(react@19.0.0)
56 | next:
57 | specifier: 15.3.0-canary.0
58 | version: 15.3.0-canary.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
59 | postcss:
60 | specifier: ^8.4.49
61 | version: 8.4.49
62 | postgres:
63 | specifier: ^3.4.5
64 | version: 3.4.5
65 | react:
66 | specifier: 19.0.0
67 | version: 19.0.0
68 | react-dom:
69 | specifier: 19.0.0
70 | version: 19.0.0(react@19.0.0)
71 | sonner:
72 | specifier: ^1.7.0
73 | version: 1.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
74 | tailwind-merge:
75 | specifier: ^3.0.2
76 | version: 3.0.2
77 | tailwindcss:
78 | specifier: ^4.0.13
79 | version: 4.0.13
80 | tailwindcss-animate:
81 | specifier: ^1.0.7
82 | version: 1.0.7(tailwindcss@4.0.13)
83 | typescript:
84 | specifier: ^5.8.2
85 | version: 5.8.2
86 | zod:
87 | specifier: ^3.23.8
88 | version: 3.23.8
89 | devDependencies:
90 | prettier-plugin-organize-imports:
91 | specifier: ^4.1.0
92 | version: 4.1.0(prettier@3.5.3)(typescript@5.8.2)
93 | prettier-plugin-tailwindcss:
94 | specifier: ^0.6.11
95 | version: 0.6.11(prettier-plugin-organize-imports@4.1.0(prettier@3.5.3)(typescript@5.8.2))(prettier@3.5.3)
96 |
97 | packages:
98 |
99 | '@alloc/quick-lru@5.2.0':
100 | resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
101 | engines: {node: '>=10'}
102 |
103 | '@drizzle-team/brocli@0.10.2':
104 | resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
105 |
106 | '@emnapi/runtime@1.3.1':
107 | resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
108 |
109 | '@esbuild-kit/core-utils@3.3.2':
110 | resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
111 | deprecated: 'Merged into tsx: https://tsx.is'
112 |
113 | '@esbuild-kit/esm-loader@2.6.5':
114 | resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
115 | deprecated: 'Merged into tsx: https://tsx.is'
116 |
117 | '@esbuild/aix-ppc64@0.19.12':
118 | resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
119 | engines: {node: '>=12'}
120 | cpu: [ppc64]
121 | os: [aix]
122 |
123 | '@esbuild/android-arm64@0.18.20':
124 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
125 | engines: {node: '>=12'}
126 | cpu: [arm64]
127 | os: [android]
128 |
129 | '@esbuild/android-arm64@0.19.12':
130 | resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
131 | engines: {node: '>=12'}
132 | cpu: [arm64]
133 | os: [android]
134 |
135 | '@esbuild/android-arm@0.18.20':
136 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
137 | engines: {node: '>=12'}
138 | cpu: [arm]
139 | os: [android]
140 |
141 | '@esbuild/android-arm@0.19.12':
142 | resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
143 | engines: {node: '>=12'}
144 | cpu: [arm]
145 | os: [android]
146 |
147 | '@esbuild/android-x64@0.18.20':
148 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
149 | engines: {node: '>=12'}
150 | cpu: [x64]
151 | os: [android]
152 |
153 | '@esbuild/android-x64@0.19.12':
154 | resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
155 | engines: {node: '>=12'}
156 | cpu: [x64]
157 | os: [android]
158 |
159 | '@esbuild/darwin-arm64@0.18.20':
160 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
161 | engines: {node: '>=12'}
162 | cpu: [arm64]
163 | os: [darwin]
164 |
165 | '@esbuild/darwin-arm64@0.19.12':
166 | resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
167 | engines: {node: '>=12'}
168 | cpu: [arm64]
169 | os: [darwin]
170 |
171 | '@esbuild/darwin-x64@0.18.20':
172 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
173 | engines: {node: '>=12'}
174 | cpu: [x64]
175 | os: [darwin]
176 |
177 | '@esbuild/darwin-x64@0.19.12':
178 | resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
179 | engines: {node: '>=12'}
180 | cpu: [x64]
181 | os: [darwin]
182 |
183 | '@esbuild/freebsd-arm64@0.18.20':
184 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
185 | engines: {node: '>=12'}
186 | cpu: [arm64]
187 | os: [freebsd]
188 |
189 | '@esbuild/freebsd-arm64@0.19.12':
190 | resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
191 | engines: {node: '>=12'}
192 | cpu: [arm64]
193 | os: [freebsd]
194 |
195 | '@esbuild/freebsd-x64@0.18.20':
196 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
197 | engines: {node: '>=12'}
198 | cpu: [x64]
199 | os: [freebsd]
200 |
201 | '@esbuild/freebsd-x64@0.19.12':
202 | resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
203 | engines: {node: '>=12'}
204 | cpu: [x64]
205 | os: [freebsd]
206 |
207 | '@esbuild/linux-arm64@0.18.20':
208 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
209 | engines: {node: '>=12'}
210 | cpu: [arm64]
211 | os: [linux]
212 |
213 | '@esbuild/linux-arm64@0.19.12':
214 | resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
215 | engines: {node: '>=12'}
216 | cpu: [arm64]
217 | os: [linux]
218 |
219 | '@esbuild/linux-arm@0.18.20':
220 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
221 | engines: {node: '>=12'}
222 | cpu: [arm]
223 | os: [linux]
224 |
225 | '@esbuild/linux-arm@0.19.12':
226 | resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
227 | engines: {node: '>=12'}
228 | cpu: [arm]
229 | os: [linux]
230 |
231 | '@esbuild/linux-ia32@0.18.20':
232 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
233 | engines: {node: '>=12'}
234 | cpu: [ia32]
235 | os: [linux]
236 |
237 | '@esbuild/linux-ia32@0.19.12':
238 | resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
239 | engines: {node: '>=12'}
240 | cpu: [ia32]
241 | os: [linux]
242 |
243 | '@esbuild/linux-loong64@0.18.20':
244 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
245 | engines: {node: '>=12'}
246 | cpu: [loong64]
247 | os: [linux]
248 |
249 | '@esbuild/linux-loong64@0.19.12':
250 | resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
251 | engines: {node: '>=12'}
252 | cpu: [loong64]
253 | os: [linux]
254 |
255 | '@esbuild/linux-mips64el@0.18.20':
256 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
257 | engines: {node: '>=12'}
258 | cpu: [mips64el]
259 | os: [linux]
260 |
261 | '@esbuild/linux-mips64el@0.19.12':
262 | resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
263 | engines: {node: '>=12'}
264 | cpu: [mips64el]
265 | os: [linux]
266 |
267 | '@esbuild/linux-ppc64@0.18.20':
268 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
269 | engines: {node: '>=12'}
270 | cpu: [ppc64]
271 | os: [linux]
272 |
273 | '@esbuild/linux-ppc64@0.19.12':
274 | resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
275 | engines: {node: '>=12'}
276 | cpu: [ppc64]
277 | os: [linux]
278 |
279 | '@esbuild/linux-riscv64@0.18.20':
280 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
281 | engines: {node: '>=12'}
282 | cpu: [riscv64]
283 | os: [linux]
284 |
285 | '@esbuild/linux-riscv64@0.19.12':
286 | resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
287 | engines: {node: '>=12'}
288 | cpu: [riscv64]
289 | os: [linux]
290 |
291 | '@esbuild/linux-s390x@0.18.20':
292 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
293 | engines: {node: '>=12'}
294 | cpu: [s390x]
295 | os: [linux]
296 |
297 | '@esbuild/linux-s390x@0.19.12':
298 | resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
299 | engines: {node: '>=12'}
300 | cpu: [s390x]
301 | os: [linux]
302 |
303 | '@esbuild/linux-x64@0.18.20':
304 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
305 | engines: {node: '>=12'}
306 | cpu: [x64]
307 | os: [linux]
308 |
309 | '@esbuild/linux-x64@0.19.12':
310 | resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
311 | engines: {node: '>=12'}
312 | cpu: [x64]
313 | os: [linux]
314 |
315 | '@esbuild/netbsd-x64@0.18.20':
316 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
317 | engines: {node: '>=12'}
318 | cpu: [x64]
319 | os: [netbsd]
320 |
321 | '@esbuild/netbsd-x64@0.19.12':
322 | resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
323 | engines: {node: '>=12'}
324 | cpu: [x64]
325 | os: [netbsd]
326 |
327 | '@esbuild/openbsd-x64@0.18.20':
328 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
329 | engines: {node: '>=12'}
330 | cpu: [x64]
331 | os: [openbsd]
332 |
333 | '@esbuild/openbsd-x64@0.19.12':
334 | resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
335 | engines: {node: '>=12'}
336 | cpu: [x64]
337 | os: [openbsd]
338 |
339 | '@esbuild/sunos-x64@0.18.20':
340 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
341 | engines: {node: '>=12'}
342 | cpu: [x64]
343 | os: [sunos]
344 |
345 | '@esbuild/sunos-x64@0.19.12':
346 | resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
347 | engines: {node: '>=12'}
348 | cpu: [x64]
349 | os: [sunos]
350 |
351 | '@esbuild/win32-arm64@0.18.20':
352 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
353 | engines: {node: '>=12'}
354 | cpu: [arm64]
355 | os: [win32]
356 |
357 | '@esbuild/win32-arm64@0.19.12':
358 | resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
359 | engines: {node: '>=12'}
360 | cpu: [arm64]
361 | os: [win32]
362 |
363 | '@esbuild/win32-ia32@0.18.20':
364 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
365 | engines: {node: '>=12'}
366 | cpu: [ia32]
367 | os: [win32]
368 |
369 | '@esbuild/win32-ia32@0.19.12':
370 | resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
371 | engines: {node: '>=12'}
372 | cpu: [ia32]
373 | os: [win32]
374 |
375 | '@esbuild/win32-x64@0.18.20':
376 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
377 | engines: {node: '>=12'}
378 | cpu: [x64]
379 | os: [win32]
380 |
381 | '@esbuild/win32-x64@0.19.12':
382 | resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
383 | engines: {node: '>=12'}
384 | cpu: [x64]
385 | os: [win32]
386 |
387 | '@floating-ui/core@1.6.9':
388 | resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
389 |
390 | '@floating-ui/dom@1.6.13':
391 | resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
392 |
393 | '@floating-ui/react-dom@2.1.2':
394 | resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
395 | peerDependencies:
396 | react: '>=16.8.0'
397 | react-dom: '>=16.8.0'
398 |
399 | '@floating-ui/utils@0.2.9':
400 | resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
401 |
402 | '@img/sharp-darwin-arm64@0.33.5':
403 | resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
404 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
405 | cpu: [arm64]
406 | os: [darwin]
407 |
408 | '@img/sharp-darwin-x64@0.33.5':
409 | resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
410 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
411 | cpu: [x64]
412 | os: [darwin]
413 |
414 | '@img/sharp-libvips-darwin-arm64@1.0.4':
415 | resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
416 | cpu: [arm64]
417 | os: [darwin]
418 |
419 | '@img/sharp-libvips-darwin-x64@1.0.4':
420 | resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
421 | cpu: [x64]
422 | os: [darwin]
423 |
424 | '@img/sharp-libvips-linux-arm64@1.0.4':
425 | resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
426 | cpu: [arm64]
427 | os: [linux]
428 |
429 | '@img/sharp-libvips-linux-arm@1.0.5':
430 | resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
431 | cpu: [arm]
432 | os: [linux]
433 |
434 | '@img/sharp-libvips-linux-s390x@1.0.4':
435 | resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
436 | cpu: [s390x]
437 | os: [linux]
438 |
439 | '@img/sharp-libvips-linux-x64@1.0.4':
440 | resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
441 | cpu: [x64]
442 | os: [linux]
443 |
444 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
445 | resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
446 | cpu: [arm64]
447 | os: [linux]
448 |
449 | '@img/sharp-libvips-linuxmusl-x64@1.0.4':
450 | resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
451 | cpu: [x64]
452 | os: [linux]
453 |
454 | '@img/sharp-linux-arm64@0.33.5':
455 | resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
456 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
457 | cpu: [arm64]
458 | os: [linux]
459 |
460 | '@img/sharp-linux-arm@0.33.5':
461 | resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
462 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
463 | cpu: [arm]
464 | os: [linux]
465 |
466 | '@img/sharp-linux-s390x@0.33.5':
467 | resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
468 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
469 | cpu: [s390x]
470 | os: [linux]
471 |
472 | '@img/sharp-linux-x64@0.33.5':
473 | resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
474 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
475 | cpu: [x64]
476 | os: [linux]
477 |
478 | '@img/sharp-linuxmusl-arm64@0.33.5':
479 | resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
480 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
481 | cpu: [arm64]
482 | os: [linux]
483 |
484 | '@img/sharp-linuxmusl-x64@0.33.5':
485 | resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
486 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
487 | cpu: [x64]
488 | os: [linux]
489 |
490 | '@img/sharp-wasm32@0.33.5':
491 | resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
492 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
493 | cpu: [wasm32]
494 |
495 | '@img/sharp-win32-ia32@0.33.5':
496 | resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
497 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
498 | cpu: [ia32]
499 | os: [win32]
500 |
501 | '@img/sharp-win32-x64@0.33.5':
502 | resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
503 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
504 | cpu: [x64]
505 | os: [win32]
506 |
507 | '@next/env@15.3.0-canary.0':
508 | resolution: {integrity: sha512-DsS8aqEHx5JeyPel3GaHtg5p9zytWsU17b6kTFPm19OmaAdh7AaNyDw4a/0170vdB90Gi/dXe87sInRRHRetfw==}
509 |
510 | '@next/swc-darwin-arm64@15.3.0-canary.0':
511 | resolution: {integrity: sha512-b7coo/zIlV5j6ouCyo7HaZr31JSaq37AWJMtzP2midjklGqA2ZnFEAOVAWHMz/niV/SdwFBLGA08fVTX0KCbaw==}
512 | engines: {node: '>= 10'}
513 | cpu: [arm64]
514 | os: [darwin]
515 |
516 | '@next/swc-darwin-x64@15.3.0-canary.0':
517 | resolution: {integrity: sha512-CBKyXc/fUA0ehOf707hwBIRMFMG94Udv7HK7ddBUED4P6xFtk8uohxZ9865Ms/K4c/qX+WS0V3QzPTfjbdI24w==}
518 | engines: {node: '>= 10'}
519 | cpu: [x64]
520 | os: [darwin]
521 |
522 | '@next/swc-linux-arm64-gnu@15.3.0-canary.0':
523 | resolution: {integrity: sha512-FeKG+2YVcxAwAGXCvbYTy2skJ5k5J6EzRhfAqQ6pMuDQkfRZLDuMRKfv37c3BsA4H/MJy47cPXCnS5dE9WjJVQ==}
524 | engines: {node: '>= 10'}
525 | cpu: [arm64]
526 | os: [linux]
527 |
528 | '@next/swc-linux-arm64-musl@15.3.0-canary.0':
529 | resolution: {integrity: sha512-E8OZM3hYQ3ftxRTqNCVHmWC5CnC9tCrKzWBvBDUXD2SV9VMat3sv7ido22W1+9DFhYxC9qHuPNZCt1B0M/kvMQ==}
530 | engines: {node: '>= 10'}
531 | cpu: [arm64]
532 | os: [linux]
533 |
534 | '@next/swc-linux-x64-gnu@15.3.0-canary.0':
535 | resolution: {integrity: sha512-T6Zt3mdDqUpDxQCFmw+JfYLvmxsFWQaX9ECR2nXHoztRa75GGAnr0DBr8T1OJXH5YVLSz3bDsAuwzuwdSSgW4w==}
536 | engines: {node: '>= 10'}
537 | cpu: [x64]
538 | os: [linux]
539 |
540 | '@next/swc-linux-x64-musl@15.3.0-canary.0':
541 | resolution: {integrity: sha512-4SvZWk0BBFuByZHRh+00tr6e93AnIgCRka7aVNOxvohmUqJpKDW4TPCz+SzRvS2qKcvoEgmQ6tS1eYUk6V4czQ==}
542 | engines: {node: '>= 10'}
543 | cpu: [x64]
544 | os: [linux]
545 |
546 | '@next/swc-win32-arm64-msvc@15.3.0-canary.0':
547 | resolution: {integrity: sha512-s/SYd2gbUUfB/pRLUs8s9LP2jkuo+rUYsoOrvTte3gjzpIfc7XMz9KVZtWw6gWIKtKsy+ip0pAGx/qFsEQOeXA==}
548 | engines: {node: '>= 10'}
549 | cpu: [arm64]
550 | os: [win32]
551 |
552 | '@next/swc-win32-x64-msvc@15.3.0-canary.0':
553 | resolution: {integrity: sha512-Kz4lnTvVpY4dr3mHs9MvuJbLXM0JWzBwNJntt4dBmRRUmYqTTpvdx6nXwLBw6S6uOUq3fhKljxCGHV4qiowK6g==}
554 | engines: {node: '>= 10'}
555 | cpu: [x64]
556 | os: [win32]
557 |
558 | '@radix-ui/primitive@1.1.1':
559 | resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
560 |
561 | '@radix-ui/react-arrow@1.1.2':
562 | resolution: {integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==}
563 | peerDependencies:
564 | '@types/react': '*'
565 | '@types/react-dom': '*'
566 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
567 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
568 | peerDependenciesMeta:
569 | '@types/react':
570 | optional: true
571 | '@types/react-dom':
572 | optional: true
573 |
574 | '@radix-ui/react-compose-refs@1.1.1':
575 | resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
576 | peerDependencies:
577 | '@types/react': '*'
578 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
579 | peerDependenciesMeta:
580 | '@types/react':
581 | optional: true
582 |
583 | '@radix-ui/react-context@1.1.1':
584 | resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
585 | peerDependencies:
586 | '@types/react': '*'
587 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
588 | peerDependenciesMeta:
589 | '@types/react':
590 | optional: true
591 |
592 | '@radix-ui/react-dialog@1.1.6':
593 | resolution: {integrity: sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==}
594 | peerDependencies:
595 | '@types/react': '*'
596 | '@types/react-dom': '*'
597 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
598 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
599 | peerDependenciesMeta:
600 | '@types/react':
601 | optional: true
602 | '@types/react-dom':
603 | optional: true
604 |
605 | '@radix-ui/react-dismissable-layer@1.1.5':
606 | resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==}
607 | peerDependencies:
608 | '@types/react': '*'
609 | '@types/react-dom': '*'
610 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
611 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
612 | peerDependenciesMeta:
613 | '@types/react':
614 | optional: true
615 | '@types/react-dom':
616 | optional: true
617 |
618 | '@radix-ui/react-focus-guards@1.1.1':
619 | resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
620 | peerDependencies:
621 | '@types/react': '*'
622 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
623 | peerDependenciesMeta:
624 | '@types/react':
625 | optional: true
626 |
627 | '@radix-ui/react-focus-scope@1.1.2':
628 | resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==}
629 | peerDependencies:
630 | '@types/react': '*'
631 | '@types/react-dom': '*'
632 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
633 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
634 | peerDependenciesMeta:
635 | '@types/react':
636 | optional: true
637 | '@types/react-dom':
638 | optional: true
639 |
640 | '@radix-ui/react-icons@1.3.2':
641 | resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==}
642 | peerDependencies:
643 | react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc
644 |
645 | '@radix-ui/react-id@1.1.0':
646 | resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
647 | peerDependencies:
648 | '@types/react': '*'
649 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
650 | peerDependenciesMeta:
651 | '@types/react':
652 | optional: true
653 |
654 | '@radix-ui/react-popper@1.2.2':
655 | resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==}
656 | peerDependencies:
657 | '@types/react': '*'
658 | '@types/react-dom': '*'
659 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
660 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
661 | peerDependenciesMeta:
662 | '@types/react':
663 | optional: true
664 | '@types/react-dom':
665 | optional: true
666 |
667 | '@radix-ui/react-portal@1.1.4':
668 | resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==}
669 | peerDependencies:
670 | '@types/react': '*'
671 | '@types/react-dom': '*'
672 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
673 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
674 | peerDependenciesMeta:
675 | '@types/react':
676 | optional: true
677 | '@types/react-dom':
678 | optional: true
679 |
680 | '@radix-ui/react-presence@1.1.2':
681 | resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
682 | peerDependencies:
683 | '@types/react': '*'
684 | '@types/react-dom': '*'
685 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
686 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
687 | peerDependenciesMeta:
688 | '@types/react':
689 | optional: true
690 | '@types/react-dom':
691 | optional: true
692 |
693 | '@radix-ui/react-primitive@2.0.2':
694 | resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==}
695 | peerDependencies:
696 | '@types/react': '*'
697 | '@types/react-dom': '*'
698 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
699 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
700 | peerDependenciesMeta:
701 | '@types/react':
702 | optional: true
703 | '@types/react-dom':
704 | optional: true
705 |
706 | '@radix-ui/react-slot@1.1.2':
707 | resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==}
708 | peerDependencies:
709 | '@types/react': '*'
710 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
711 | peerDependenciesMeta:
712 | '@types/react':
713 | optional: true
714 |
715 | '@radix-ui/react-tooltip@1.1.8':
716 | resolution: {integrity: sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==}
717 | peerDependencies:
718 | '@types/react': '*'
719 | '@types/react-dom': '*'
720 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
721 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
722 | peerDependenciesMeta:
723 | '@types/react':
724 | optional: true
725 | '@types/react-dom':
726 | optional: true
727 |
728 | '@radix-ui/react-use-callback-ref@1.1.0':
729 | resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
730 | peerDependencies:
731 | '@types/react': '*'
732 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
733 | peerDependenciesMeta:
734 | '@types/react':
735 | optional: true
736 |
737 | '@radix-ui/react-use-controllable-state@1.1.0':
738 | resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
739 | peerDependencies:
740 | '@types/react': '*'
741 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
742 | peerDependenciesMeta:
743 | '@types/react':
744 | optional: true
745 |
746 | '@radix-ui/react-use-escape-keydown@1.1.0':
747 | resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
748 | peerDependencies:
749 | '@types/react': '*'
750 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
751 | peerDependenciesMeta:
752 | '@types/react':
753 | optional: true
754 |
755 | '@radix-ui/react-use-layout-effect@1.1.0':
756 | resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
757 | peerDependencies:
758 | '@types/react': '*'
759 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
760 | peerDependenciesMeta:
761 | '@types/react':
762 | optional: true
763 |
764 | '@radix-ui/react-use-rect@1.1.0':
765 | resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
766 | peerDependencies:
767 | '@types/react': '*'
768 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
769 | peerDependenciesMeta:
770 | '@types/react':
771 | optional: true
772 |
773 | '@radix-ui/react-use-size@1.1.0':
774 | resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
775 | peerDependencies:
776 | '@types/react': '*'
777 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
778 | peerDependenciesMeta:
779 | '@types/react':
780 | optional: true
781 |
782 | '@radix-ui/react-visually-hidden@1.1.2':
783 | resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==}
784 | peerDependencies:
785 | '@types/react': '*'
786 | '@types/react-dom': '*'
787 | react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
788 | react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
789 | peerDependenciesMeta:
790 | '@types/react':
791 | optional: true
792 | '@types/react-dom':
793 | optional: true
794 |
795 | '@radix-ui/rect@1.1.0':
796 | resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
797 |
798 | '@swc/counter@0.1.3':
799 | resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
800 |
801 | '@swc/helpers@0.5.15':
802 | resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
803 |
804 | '@tailwindcss/node@4.0.13':
805 | resolution: {integrity: sha512-P9TmtE9Vew0vv5FwyD4bsg/dHHsIsAuUXkenuGUc5gm8fYgaxpdoxIKngCyEMEQxyCKR8PQY5V5VrrKNOx7exg==}
806 |
807 | '@tailwindcss/oxide-android-arm64@4.0.13':
808 | resolution: {integrity: sha512-+9zmwaPQ8A9ycDcdb+hRkMn6NzsmZ4YJBsW5Xqq5EdOu9xlIgmuMuJauVzDPB5BSbIWfhPdZ+le8NeRZpl1coA==}
809 | engines: {node: '>= 10'}
810 | cpu: [arm64]
811 | os: [android]
812 |
813 | '@tailwindcss/oxide-darwin-arm64@4.0.13':
814 | resolution: {integrity: sha512-Bj1QGlEJSjs/205CIRfb5/jeveOqzJ4pFMdRxu0gyiYWxBRyxsExXqaD+7162wnLP/EDKh6S1MC9E/1GwEhLtA==}
815 | engines: {node: '>= 10'}
816 | cpu: [arm64]
817 | os: [darwin]
818 |
819 | '@tailwindcss/oxide-darwin-x64@4.0.13':
820 | resolution: {integrity: sha512-lRTkxjTpMGXhLLM5GjZ0MtjPczMuhAo9j7PeSsaU6Imkm7W7RbrXfT8aP934kS7cBBV+HKN5U19Z0WWaORfb8Q==}
821 | engines: {node: '>= 10'}
822 | cpu: [x64]
823 | os: [darwin]
824 |
825 | '@tailwindcss/oxide-freebsd-x64@4.0.13':
826 | resolution: {integrity: sha512-p/YLyKhs+xFibVeAPlpMGDVMKgjChgzs12VnDFaaqRSJoOz+uJgRSKiir2tn50e7Nm4YYw35q/DRBwpDBNo1MQ==}
827 | engines: {node: '>= 10'}
828 | cpu: [x64]
829 | os: [freebsd]
830 |
831 | '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.13':
832 | resolution: {integrity: sha512-Ua/5ydE/QOTX8jHuc7M9ICWnaLi6K2MV/r+Ws2OppsOjy8tdlPbqYainJJ6Kl7ofm524K+4Fk9CQITPzeIESPw==}
833 | engines: {node: '>= 10'}
834 | cpu: [arm]
835 | os: [linux]
836 |
837 | '@tailwindcss/oxide-linux-arm64-gnu@4.0.13':
838 | resolution: {integrity: sha512-/W1+Q6tBAVgZWh/bhfOHo4n7Ryh6E7zYj4bJd9SRbkPyLtRioyK3bi6RLuDj57sa7Amk/DeomSV9iycS0xqIPA==}
839 | engines: {node: '>= 10'}
840 | cpu: [arm64]
841 | os: [linux]
842 |
843 | '@tailwindcss/oxide-linux-arm64-musl@4.0.13':
844 | resolution: {integrity: sha512-GQj6TWevNxwsYw20FdT2r2d1f7uiRsF07iFvNYxPIvIyPEV74eZ0zgFEsAH1daK1OxPy+LXdZ4grV17P5tVzhQ==}
845 | engines: {node: '>= 10'}
846 | cpu: [arm64]
847 | os: [linux]
848 |
849 | '@tailwindcss/oxide-linux-x64-gnu@4.0.13':
850 | resolution: {integrity: sha512-sQRH09faifF9w9WS6TKDWr1oLi4hoPx0EIWXZHQK/jcjarDpXGQ2DbF0KnALJCwWBxOIP/1nrmU01fZwwMzY3g==}
851 | engines: {node: '>= 10'}
852 | cpu: [x64]
853 | os: [linux]
854 |
855 | '@tailwindcss/oxide-linux-x64-musl@4.0.13':
856 | resolution: {integrity: sha512-Or1N8DIF3tP+LsloJp+UXLTIMMHMUcWXFhJLCsM4T7MzFzxkeReewRWXfk5mk137cdqVeUEH/R50xAhY1mOkTQ==}
857 | engines: {node: '>= 10'}
858 | cpu: [x64]
859 | os: [linux]
860 |
861 | '@tailwindcss/oxide-win32-arm64-msvc@4.0.13':
862 | resolution: {integrity: sha512-u2mQyqCFrr9vVTP6sfDRfGE6bhOX3/7rInehzxNhHX1HYRIx09H3sDdXzTxnZWKOjIg3qjFTCrYFUZckva5PIg==}
863 | engines: {node: '>= 10'}
864 | cpu: [arm64]
865 | os: [win32]
866 |
867 | '@tailwindcss/oxide-win32-x64-msvc@4.0.13':
868 | resolution: {integrity: sha512-sOEc4iCanp1Yqyeu9suQcEzfaUcHnqjBUgDg0ZXpjUMUwdSi37S1lu1RGoV1BYInvvGu3y3HHTmvsSfDhx2L8w==}
869 | engines: {node: '>= 10'}
870 | cpu: [x64]
871 | os: [win32]
872 |
873 | '@tailwindcss/oxide@4.0.13':
874 | resolution: {integrity: sha512-pTH3Ex5zAWC9LbS+WsYAFmkXQW3NRjmvxkKJY3NP1x0KHBWjz0Q2uGtdGMJzsa0EwoZ7wq9RTbMH1UNPceCpWw==}
875 | engines: {node: '>= 10'}
876 |
877 | '@tailwindcss/postcss@4.0.13':
878 | resolution: {integrity: sha512-zTmnPGDYb2HKClTBTBwB+lLQH+Rq4etnQXFXs2lisRyXryUnoJIBByFTljkaK9F1d7o14h6t4NJIlfbZuOHR+A==}
879 |
880 | '@types/node@22.10.1':
881 | resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
882 |
883 | '@types/react-dom@19.0.4':
884 | resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
885 | peerDependencies:
886 | '@types/react': ^19.0.0
887 |
888 | '@types/react@19.0.10':
889 | resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==}
890 |
891 | aria-hidden@1.2.4:
892 | resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
893 | engines: {node: '>=10'}
894 |
895 | buffer-from@1.1.2:
896 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
897 |
898 | busboy@1.6.0:
899 | resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
900 | engines: {node: '>=10.16.0'}
901 |
902 | caniuse-lite@1.0.30001703:
903 | resolution: {integrity: sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==}
904 |
905 | class-variance-authority@0.7.1:
906 | resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
907 |
908 | client-only@0.0.1:
909 | resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
910 |
911 | clsx@2.1.1:
912 | resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
913 | engines: {node: '>=6'}
914 |
915 | color-convert@2.0.1:
916 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
917 | engines: {node: '>=7.0.0'}
918 |
919 | color-name@1.1.4:
920 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
921 |
922 | color-string@1.9.1:
923 | resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
924 |
925 | color@4.2.3:
926 | resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
927 | engines: {node: '>=12.5.0'}
928 |
929 | csstype@3.1.3:
930 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
931 |
932 | debug@4.3.7:
933 | resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
934 | engines: {node: '>=6.0'}
935 | peerDependencies:
936 | supports-color: '*'
937 | peerDependenciesMeta:
938 | supports-color:
939 | optional: true
940 |
941 | detect-libc@2.0.3:
942 | resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
943 | engines: {node: '>=8'}
944 |
945 | detect-node-es@1.1.0:
946 | resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
947 |
948 | dotenv@16.4.5:
949 | resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
950 | engines: {node: '>=12'}
951 |
952 | drizzle-kit@0.28.1:
953 | resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==}
954 | hasBin: true
955 |
956 | drizzle-orm@0.36.4:
957 | resolution: {integrity: sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==}
958 | peerDependencies:
959 | '@aws-sdk/client-rds-data': '>=3'
960 | '@cloudflare/workers-types': '>=3'
961 | '@electric-sql/pglite': '>=0.2.0'
962 | '@libsql/client': '>=0.10.0'
963 | '@libsql/client-wasm': '>=0.10.0'
964 | '@neondatabase/serverless': '>=0.10.0'
965 | '@op-engineering/op-sqlite': '>=2'
966 | '@opentelemetry/api': ^1.4.1
967 | '@planetscale/database': '>=1'
968 | '@prisma/client': '*'
969 | '@tidbcloud/serverless': '*'
970 | '@types/better-sqlite3': '*'
971 | '@types/pg': '*'
972 | '@types/react': '>=18'
973 | '@types/sql.js': '*'
974 | '@vercel/postgres': '>=0.8.0'
975 | '@xata.io/client': '*'
976 | better-sqlite3: '>=7'
977 | bun-types: '*'
978 | expo-sqlite: '>=14.0.0'
979 | knex: '*'
980 | kysely: '*'
981 | mysql2: '>=2'
982 | pg: '>=8'
983 | postgres: '>=3'
984 | prisma: '*'
985 | react: '>=18'
986 | sql.js: '>=1'
987 | sqlite3: '>=5'
988 | peerDependenciesMeta:
989 | '@aws-sdk/client-rds-data':
990 | optional: true
991 | '@cloudflare/workers-types':
992 | optional: true
993 | '@electric-sql/pglite':
994 | optional: true
995 | '@libsql/client':
996 | optional: true
997 | '@libsql/client-wasm':
998 | optional: true
999 | '@neondatabase/serverless':
1000 | optional: true
1001 | '@op-engineering/op-sqlite':
1002 | optional: true
1003 | '@opentelemetry/api':
1004 | optional: true
1005 | '@planetscale/database':
1006 | optional: true
1007 | '@prisma/client':
1008 | optional: true
1009 | '@tidbcloud/serverless':
1010 | optional: true
1011 | '@types/better-sqlite3':
1012 | optional: true
1013 | '@types/pg':
1014 | optional: true
1015 | '@types/react':
1016 | optional: true
1017 | '@types/sql.js':
1018 | optional: true
1019 | '@vercel/postgres':
1020 | optional: true
1021 | '@xata.io/client':
1022 | optional: true
1023 | better-sqlite3:
1024 | optional: true
1025 | bun-types:
1026 | optional: true
1027 | expo-sqlite:
1028 | optional: true
1029 | knex:
1030 | optional: true
1031 | kysely:
1032 | optional: true
1033 | mysql2:
1034 | optional: true
1035 | pg:
1036 | optional: true
1037 | postgres:
1038 | optional: true
1039 | prisma:
1040 | optional: true
1041 | react:
1042 | optional: true
1043 | sql.js:
1044 | optional: true
1045 | sqlite3:
1046 | optional: true
1047 |
1048 | enhanced-resolve@5.18.1:
1049 | resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
1050 | engines: {node: '>=10.13.0'}
1051 |
1052 | esbuild-register@3.6.0:
1053 | resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
1054 | peerDependencies:
1055 | esbuild: '>=0.12 <1'
1056 |
1057 | esbuild@0.18.20:
1058 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
1059 | engines: {node: '>=12'}
1060 | hasBin: true
1061 |
1062 | esbuild@0.19.12:
1063 | resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
1064 | engines: {node: '>=12'}
1065 | hasBin: true
1066 |
1067 | geist@1.3.1:
1068 | resolution: {integrity: sha512-Q4gC1pBVPN+D579pBaz0TRRnGA4p9UK6elDY/xizXdFk/g4EKR5g0I+4p/Kj6gM0SajDBZ/0FvDV9ey9ud7BWw==}
1069 | peerDependencies:
1070 | next: '>=13.2.0'
1071 |
1072 | get-nonce@1.0.1:
1073 | resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
1074 | engines: {node: '>=6'}
1075 |
1076 | get-tsconfig@4.8.1:
1077 | resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==}
1078 |
1079 | graceful-fs@4.2.11:
1080 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
1081 |
1082 | is-arrayish@0.3.2:
1083 | resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
1084 |
1085 | jiti@2.4.2:
1086 | resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
1087 | hasBin: true
1088 |
1089 | lightningcss-darwin-arm64@1.29.2:
1090 | resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
1091 | engines: {node: '>= 12.0.0'}
1092 | cpu: [arm64]
1093 | os: [darwin]
1094 |
1095 | lightningcss-darwin-x64@1.29.2:
1096 | resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
1097 | engines: {node: '>= 12.0.0'}
1098 | cpu: [x64]
1099 | os: [darwin]
1100 |
1101 | lightningcss-freebsd-x64@1.29.2:
1102 | resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==}
1103 | engines: {node: '>= 12.0.0'}
1104 | cpu: [x64]
1105 | os: [freebsd]
1106 |
1107 | lightningcss-linux-arm-gnueabihf@1.29.2:
1108 | resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==}
1109 | engines: {node: '>= 12.0.0'}
1110 | cpu: [arm]
1111 | os: [linux]
1112 |
1113 | lightningcss-linux-arm64-gnu@1.29.2:
1114 | resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
1115 | engines: {node: '>= 12.0.0'}
1116 | cpu: [arm64]
1117 | os: [linux]
1118 |
1119 | lightningcss-linux-arm64-musl@1.29.2:
1120 | resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
1121 | engines: {node: '>= 12.0.0'}
1122 | cpu: [arm64]
1123 | os: [linux]
1124 |
1125 | lightningcss-linux-x64-gnu@1.29.2:
1126 | resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
1127 | engines: {node: '>= 12.0.0'}
1128 | cpu: [x64]
1129 | os: [linux]
1130 |
1131 | lightningcss-linux-x64-musl@1.29.2:
1132 | resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
1133 | engines: {node: '>= 12.0.0'}
1134 | cpu: [x64]
1135 | os: [linux]
1136 |
1137 | lightningcss-win32-arm64-msvc@1.29.2:
1138 | resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
1139 | engines: {node: '>= 12.0.0'}
1140 | cpu: [arm64]
1141 | os: [win32]
1142 |
1143 | lightningcss-win32-x64-msvc@1.29.2:
1144 | resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
1145 | engines: {node: '>= 12.0.0'}
1146 | cpu: [x64]
1147 | os: [win32]
1148 |
1149 | lightningcss@1.29.2:
1150 | resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
1151 | engines: {node: '>= 12.0.0'}
1152 |
1153 | lucide-react@0.462.0:
1154 | resolution: {integrity: sha512-NTL7EbAao9IFtuSivSZgrAh4fZd09Lr+6MTkqIxuHaH2nnYiYIzXPo06cOxHg9wKLdj6LL8TByG4qpePqwgx/g==}
1155 | peerDependencies:
1156 | react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
1157 |
1158 | ms@2.1.3:
1159 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1160 |
1161 | nanoid@3.3.8:
1162 | resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
1163 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1164 | hasBin: true
1165 |
1166 | nanoid@3.3.9:
1167 | resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==}
1168 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1169 | hasBin: true
1170 |
1171 | next@15.3.0-canary.0:
1172 | resolution: {integrity: sha512-5PT9X65OBHwY22hu3O04ylZ4zznRhKdMOR1A4Hagw3eHrXO4UYtkcnhMErWq4BbrgYKYg+5XJ+kgNcHN7H8big==}
1173 | engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
1174 | hasBin: true
1175 | peerDependencies:
1176 | '@opentelemetry/api': ^1.1.0
1177 | '@playwright/test': ^1.41.2
1178 | babel-plugin-react-compiler: '*'
1179 | react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
1180 | react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
1181 | sass: ^1.3.0
1182 | peerDependenciesMeta:
1183 | '@opentelemetry/api':
1184 | optional: true
1185 | '@playwright/test':
1186 | optional: true
1187 | babel-plugin-react-compiler:
1188 | optional: true
1189 | sass:
1190 | optional: true
1191 |
1192 | picocolors@1.1.1:
1193 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
1194 |
1195 | postcss@8.4.31:
1196 | resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
1197 | engines: {node: ^10 || ^12 || >=14}
1198 |
1199 | postcss@8.4.49:
1200 | resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
1201 | engines: {node: ^10 || ^12 || >=14}
1202 |
1203 | postgres@3.4.5:
1204 | resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==}
1205 | engines: {node: '>=12'}
1206 |
1207 | prettier-plugin-organize-imports@4.1.0:
1208 | resolution: {integrity: sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==}
1209 | peerDependencies:
1210 | prettier: '>=2.0'
1211 | typescript: '>=2.9'
1212 | vue-tsc: ^2.1.0
1213 | peerDependenciesMeta:
1214 | vue-tsc:
1215 | optional: true
1216 |
1217 | prettier-plugin-tailwindcss@0.6.11:
1218 | resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==}
1219 | engines: {node: '>=14.21.3'}
1220 | peerDependencies:
1221 | '@ianvs/prettier-plugin-sort-imports': '*'
1222 | '@prettier/plugin-pug': '*'
1223 | '@shopify/prettier-plugin-liquid': '*'
1224 | '@trivago/prettier-plugin-sort-imports': '*'
1225 | '@zackad/prettier-plugin-twig': '*'
1226 | prettier: ^3.0
1227 | prettier-plugin-astro: '*'
1228 | prettier-plugin-css-order: '*'
1229 | prettier-plugin-import-sort: '*'
1230 | prettier-plugin-jsdoc: '*'
1231 | prettier-plugin-marko: '*'
1232 | prettier-plugin-multiline-arrays: '*'
1233 | prettier-plugin-organize-attributes: '*'
1234 | prettier-plugin-organize-imports: '*'
1235 | prettier-plugin-sort-imports: '*'
1236 | prettier-plugin-style-order: '*'
1237 | prettier-plugin-svelte: '*'
1238 | peerDependenciesMeta:
1239 | '@ianvs/prettier-plugin-sort-imports':
1240 | optional: true
1241 | '@prettier/plugin-pug':
1242 | optional: true
1243 | '@shopify/prettier-plugin-liquid':
1244 | optional: true
1245 | '@trivago/prettier-plugin-sort-imports':
1246 | optional: true
1247 | '@zackad/prettier-plugin-twig':
1248 | optional: true
1249 | prettier-plugin-astro:
1250 | optional: true
1251 | prettier-plugin-css-order:
1252 | optional: true
1253 | prettier-plugin-import-sort:
1254 | optional: true
1255 | prettier-plugin-jsdoc:
1256 | optional: true
1257 | prettier-plugin-marko:
1258 | optional: true
1259 | prettier-plugin-multiline-arrays:
1260 | optional: true
1261 | prettier-plugin-organize-attributes:
1262 | optional: true
1263 | prettier-plugin-organize-imports:
1264 | optional: true
1265 | prettier-plugin-sort-imports:
1266 | optional: true
1267 | prettier-plugin-style-order:
1268 | optional: true
1269 | prettier-plugin-svelte:
1270 | optional: true
1271 |
1272 | prettier@3.5.3:
1273 | resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
1274 | engines: {node: '>=14'}
1275 | hasBin: true
1276 |
1277 | react-dom@19.0.0:
1278 | resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
1279 | peerDependencies:
1280 | react: ^19.0.0
1281 |
1282 | react-remove-scroll-bar@2.3.8:
1283 | resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
1284 | engines: {node: '>=10'}
1285 | peerDependencies:
1286 | '@types/react': '*'
1287 | react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
1288 | peerDependenciesMeta:
1289 | '@types/react':
1290 | optional: true
1291 |
1292 | react-remove-scroll@2.6.3:
1293 | resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==}
1294 | engines: {node: '>=10'}
1295 | peerDependencies:
1296 | '@types/react': '*'
1297 | react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
1298 | peerDependenciesMeta:
1299 | '@types/react':
1300 | optional: true
1301 |
1302 | react-style-singleton@2.2.3:
1303 | resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
1304 | engines: {node: '>=10'}
1305 | peerDependencies:
1306 | '@types/react': '*'
1307 | react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
1308 | peerDependenciesMeta:
1309 | '@types/react':
1310 | optional: true
1311 |
1312 | react@19.0.0:
1313 | resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
1314 | engines: {node: '>=0.10.0'}
1315 |
1316 | resolve-pkg-maps@1.0.0:
1317 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
1318 |
1319 | scheduler@0.25.0:
1320 | resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
1321 |
1322 | semver@7.7.1:
1323 | resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
1324 | engines: {node: '>=10'}
1325 | hasBin: true
1326 |
1327 | sharp@0.33.5:
1328 | resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
1329 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
1330 |
1331 | simple-swizzle@0.2.2:
1332 | resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
1333 |
1334 | sonner@1.7.0:
1335 | resolution: {integrity: sha512-W6dH7m5MujEPyug3lpI2l3TC3Pp1+LTgK0Efg+IHDrBbtEjyCmCHHo6yfNBOsf1tFZ6zf+jceWwB38baC8yO9g==}
1336 | peerDependencies:
1337 | react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
1338 | react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
1339 |
1340 | source-map-js@1.2.1:
1341 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
1342 | engines: {node: '>=0.10.0'}
1343 |
1344 | source-map-support@0.5.21:
1345 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
1346 |
1347 | source-map@0.6.1:
1348 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
1349 | engines: {node: '>=0.10.0'}
1350 |
1351 | streamsearch@1.1.0:
1352 | resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
1353 | engines: {node: '>=10.0.0'}
1354 |
1355 | styled-jsx@5.1.6:
1356 | resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
1357 | engines: {node: '>= 12.0.0'}
1358 | peerDependencies:
1359 | '@babel/core': '*'
1360 | babel-plugin-macros: '*'
1361 | react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
1362 | peerDependenciesMeta:
1363 | '@babel/core':
1364 | optional: true
1365 | babel-plugin-macros:
1366 | optional: true
1367 |
1368 | tailwind-merge@3.0.2:
1369 | resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
1370 |
1371 | tailwindcss-animate@1.0.7:
1372 | resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
1373 | peerDependencies:
1374 | tailwindcss: '>=3.0.0 || insiders'
1375 |
1376 | tailwindcss@4.0.13:
1377 | resolution: {integrity: sha512-gbvFrB0fOsTv/OugXWi2PtflJ4S6/ctu6Mmn3bCftmLY/6xRsQVEJPgIIpABwpZ52DpONkCA3bEj5b54MHxF2Q==}
1378 |
1379 | tapable@2.2.1:
1380 | resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
1381 | engines: {node: '>=6'}
1382 |
1383 | tslib@2.8.1:
1384 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
1385 |
1386 | typescript@5.8.2:
1387 | resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
1388 | engines: {node: '>=14.17'}
1389 | hasBin: true
1390 |
1391 | undici-types@6.20.0:
1392 | resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
1393 |
1394 | use-callback-ref@1.3.3:
1395 | resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
1396 | engines: {node: '>=10'}
1397 | peerDependencies:
1398 | '@types/react': '*'
1399 | react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
1400 | peerDependenciesMeta:
1401 | '@types/react':
1402 | optional: true
1403 |
1404 | use-sidecar@1.1.3:
1405 | resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
1406 | engines: {node: '>=10'}
1407 | peerDependencies:
1408 | '@types/react': '*'
1409 | react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
1410 | peerDependenciesMeta:
1411 | '@types/react':
1412 | optional: true
1413 |
1414 | zod@3.23.8:
1415 | resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
1416 |
1417 | snapshots:
1418 |
1419 | '@alloc/quick-lru@5.2.0': {}
1420 |
1421 | '@drizzle-team/brocli@0.10.2': {}
1422 |
1423 | '@emnapi/runtime@1.3.1':
1424 | dependencies:
1425 | tslib: 2.8.1
1426 | optional: true
1427 |
1428 | '@esbuild-kit/core-utils@3.3.2':
1429 | dependencies:
1430 | esbuild: 0.18.20
1431 | source-map-support: 0.5.21
1432 |
1433 | '@esbuild-kit/esm-loader@2.6.5':
1434 | dependencies:
1435 | '@esbuild-kit/core-utils': 3.3.2
1436 | get-tsconfig: 4.8.1
1437 |
1438 | '@esbuild/aix-ppc64@0.19.12':
1439 | optional: true
1440 |
1441 | '@esbuild/android-arm64@0.18.20':
1442 | optional: true
1443 |
1444 | '@esbuild/android-arm64@0.19.12':
1445 | optional: true
1446 |
1447 | '@esbuild/android-arm@0.18.20':
1448 | optional: true
1449 |
1450 | '@esbuild/android-arm@0.19.12':
1451 | optional: true
1452 |
1453 | '@esbuild/android-x64@0.18.20':
1454 | optional: true
1455 |
1456 | '@esbuild/android-x64@0.19.12':
1457 | optional: true
1458 |
1459 | '@esbuild/darwin-arm64@0.18.20':
1460 | optional: true
1461 |
1462 | '@esbuild/darwin-arm64@0.19.12':
1463 | optional: true
1464 |
1465 | '@esbuild/darwin-x64@0.18.20':
1466 | optional: true
1467 |
1468 | '@esbuild/darwin-x64@0.19.12':
1469 | optional: true
1470 |
1471 | '@esbuild/freebsd-arm64@0.18.20':
1472 | optional: true
1473 |
1474 | '@esbuild/freebsd-arm64@0.19.12':
1475 | optional: true
1476 |
1477 | '@esbuild/freebsd-x64@0.18.20':
1478 | optional: true
1479 |
1480 | '@esbuild/freebsd-x64@0.19.12':
1481 | optional: true
1482 |
1483 | '@esbuild/linux-arm64@0.18.20':
1484 | optional: true
1485 |
1486 | '@esbuild/linux-arm64@0.19.12':
1487 | optional: true
1488 |
1489 | '@esbuild/linux-arm@0.18.20':
1490 | optional: true
1491 |
1492 | '@esbuild/linux-arm@0.19.12':
1493 | optional: true
1494 |
1495 | '@esbuild/linux-ia32@0.18.20':
1496 | optional: true
1497 |
1498 | '@esbuild/linux-ia32@0.19.12':
1499 | optional: true
1500 |
1501 | '@esbuild/linux-loong64@0.18.20':
1502 | optional: true
1503 |
1504 | '@esbuild/linux-loong64@0.19.12':
1505 | optional: true
1506 |
1507 | '@esbuild/linux-mips64el@0.18.20':
1508 | optional: true
1509 |
1510 | '@esbuild/linux-mips64el@0.19.12':
1511 | optional: true
1512 |
1513 | '@esbuild/linux-ppc64@0.18.20':
1514 | optional: true
1515 |
1516 | '@esbuild/linux-ppc64@0.19.12':
1517 | optional: true
1518 |
1519 | '@esbuild/linux-riscv64@0.18.20':
1520 | optional: true
1521 |
1522 | '@esbuild/linux-riscv64@0.19.12':
1523 | optional: true
1524 |
1525 | '@esbuild/linux-s390x@0.18.20':
1526 | optional: true
1527 |
1528 | '@esbuild/linux-s390x@0.19.12':
1529 | optional: true
1530 |
1531 | '@esbuild/linux-x64@0.18.20':
1532 | optional: true
1533 |
1534 | '@esbuild/linux-x64@0.19.12':
1535 | optional: true
1536 |
1537 | '@esbuild/netbsd-x64@0.18.20':
1538 | optional: true
1539 |
1540 | '@esbuild/netbsd-x64@0.19.12':
1541 | optional: true
1542 |
1543 | '@esbuild/openbsd-x64@0.18.20':
1544 | optional: true
1545 |
1546 | '@esbuild/openbsd-x64@0.19.12':
1547 | optional: true
1548 |
1549 | '@esbuild/sunos-x64@0.18.20':
1550 | optional: true
1551 |
1552 | '@esbuild/sunos-x64@0.19.12':
1553 | optional: true
1554 |
1555 | '@esbuild/win32-arm64@0.18.20':
1556 | optional: true
1557 |
1558 | '@esbuild/win32-arm64@0.19.12':
1559 | optional: true
1560 |
1561 | '@esbuild/win32-ia32@0.18.20':
1562 | optional: true
1563 |
1564 | '@esbuild/win32-ia32@0.19.12':
1565 | optional: true
1566 |
1567 | '@esbuild/win32-x64@0.18.20':
1568 | optional: true
1569 |
1570 | '@esbuild/win32-x64@0.19.12':
1571 | optional: true
1572 |
1573 | '@floating-ui/core@1.6.9':
1574 | dependencies:
1575 | '@floating-ui/utils': 0.2.9
1576 |
1577 | '@floating-ui/dom@1.6.13':
1578 | dependencies:
1579 | '@floating-ui/core': 1.6.9
1580 | '@floating-ui/utils': 0.2.9
1581 |
1582 | '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1583 | dependencies:
1584 | '@floating-ui/dom': 1.6.13
1585 | react: 19.0.0
1586 | react-dom: 19.0.0(react@19.0.0)
1587 |
1588 | '@floating-ui/utils@0.2.9': {}
1589 |
1590 | '@img/sharp-darwin-arm64@0.33.5':
1591 | optionalDependencies:
1592 | '@img/sharp-libvips-darwin-arm64': 1.0.4
1593 | optional: true
1594 |
1595 | '@img/sharp-darwin-x64@0.33.5':
1596 | optionalDependencies:
1597 | '@img/sharp-libvips-darwin-x64': 1.0.4
1598 | optional: true
1599 |
1600 | '@img/sharp-libvips-darwin-arm64@1.0.4':
1601 | optional: true
1602 |
1603 | '@img/sharp-libvips-darwin-x64@1.0.4':
1604 | optional: true
1605 |
1606 | '@img/sharp-libvips-linux-arm64@1.0.4':
1607 | optional: true
1608 |
1609 | '@img/sharp-libvips-linux-arm@1.0.5':
1610 | optional: true
1611 |
1612 | '@img/sharp-libvips-linux-s390x@1.0.4':
1613 | optional: true
1614 |
1615 | '@img/sharp-libvips-linux-x64@1.0.4':
1616 | optional: true
1617 |
1618 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
1619 | optional: true
1620 |
1621 | '@img/sharp-libvips-linuxmusl-x64@1.0.4':
1622 | optional: true
1623 |
1624 | '@img/sharp-linux-arm64@0.33.5':
1625 | optionalDependencies:
1626 | '@img/sharp-libvips-linux-arm64': 1.0.4
1627 | optional: true
1628 |
1629 | '@img/sharp-linux-arm@0.33.5':
1630 | optionalDependencies:
1631 | '@img/sharp-libvips-linux-arm': 1.0.5
1632 | optional: true
1633 |
1634 | '@img/sharp-linux-s390x@0.33.5':
1635 | optionalDependencies:
1636 | '@img/sharp-libvips-linux-s390x': 1.0.4
1637 | optional: true
1638 |
1639 | '@img/sharp-linux-x64@0.33.5':
1640 | optionalDependencies:
1641 | '@img/sharp-libvips-linux-x64': 1.0.4
1642 | optional: true
1643 |
1644 | '@img/sharp-linuxmusl-arm64@0.33.5':
1645 | optionalDependencies:
1646 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
1647 | optional: true
1648 |
1649 | '@img/sharp-linuxmusl-x64@0.33.5':
1650 | optionalDependencies:
1651 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4
1652 | optional: true
1653 |
1654 | '@img/sharp-wasm32@0.33.5':
1655 | dependencies:
1656 | '@emnapi/runtime': 1.3.1
1657 | optional: true
1658 |
1659 | '@img/sharp-win32-ia32@0.33.5':
1660 | optional: true
1661 |
1662 | '@img/sharp-win32-x64@0.33.5':
1663 | optional: true
1664 |
1665 | '@next/env@15.3.0-canary.0': {}
1666 |
1667 | '@next/swc-darwin-arm64@15.3.0-canary.0':
1668 | optional: true
1669 |
1670 | '@next/swc-darwin-x64@15.3.0-canary.0':
1671 | optional: true
1672 |
1673 | '@next/swc-linux-arm64-gnu@15.3.0-canary.0':
1674 | optional: true
1675 |
1676 | '@next/swc-linux-arm64-musl@15.3.0-canary.0':
1677 | optional: true
1678 |
1679 | '@next/swc-linux-x64-gnu@15.3.0-canary.0':
1680 | optional: true
1681 |
1682 | '@next/swc-linux-x64-musl@15.3.0-canary.0':
1683 | optional: true
1684 |
1685 | '@next/swc-win32-arm64-msvc@15.3.0-canary.0':
1686 | optional: true
1687 |
1688 | '@next/swc-win32-x64-msvc@15.3.0-canary.0':
1689 | optional: true
1690 |
1691 | '@radix-ui/primitive@1.1.1': {}
1692 |
1693 | '@radix-ui/react-arrow@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1694 | dependencies:
1695 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1696 | react: 19.0.0
1697 | react-dom: 19.0.0(react@19.0.0)
1698 | optionalDependencies:
1699 | '@types/react': 19.0.10
1700 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1701 |
1702 | '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.10)(react@19.0.0)':
1703 | dependencies:
1704 | react: 19.0.0
1705 | optionalDependencies:
1706 | '@types/react': 19.0.10
1707 |
1708 | '@radix-ui/react-context@1.1.1(@types/react@19.0.10)(react@19.0.0)':
1709 | dependencies:
1710 | react: 19.0.0
1711 | optionalDependencies:
1712 | '@types/react': 19.0.10
1713 |
1714 | '@radix-ui/react-dialog@1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1715 | dependencies:
1716 | '@radix-ui/primitive': 1.1.1
1717 | '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1718 | '@radix-ui/react-context': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1719 | '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1720 | '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1721 | '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1722 | '@radix-ui/react-id': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1723 | '@radix-ui/react-portal': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1724 | '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1725 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1726 | '@radix-ui/react-slot': 1.1.2(@types/react@19.0.10)(react@19.0.0)
1727 | '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1728 | aria-hidden: 1.2.4
1729 | react: 19.0.0
1730 | react-dom: 19.0.0(react@19.0.0)
1731 | react-remove-scroll: 2.6.3(@types/react@19.0.10)(react@19.0.0)
1732 | optionalDependencies:
1733 | '@types/react': 19.0.10
1734 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1735 |
1736 | '@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1737 | dependencies:
1738 | '@radix-ui/primitive': 1.1.1
1739 | '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1740 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1741 | '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1742 | '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1743 | react: 19.0.0
1744 | react-dom: 19.0.0(react@19.0.0)
1745 | optionalDependencies:
1746 | '@types/react': 19.0.10
1747 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1748 |
1749 | '@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.10)(react@19.0.0)':
1750 | dependencies:
1751 | react: 19.0.0
1752 | optionalDependencies:
1753 | '@types/react': 19.0.10
1754 |
1755 | '@radix-ui/react-focus-scope@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1756 | dependencies:
1757 | '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1758 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1759 | '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1760 | react: 19.0.0
1761 | react-dom: 19.0.0(react@19.0.0)
1762 | optionalDependencies:
1763 | '@types/react': 19.0.10
1764 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1765 |
1766 | '@radix-ui/react-icons@1.3.2(react@19.0.0)':
1767 | dependencies:
1768 | react: 19.0.0
1769 |
1770 | '@radix-ui/react-id@1.1.0(@types/react@19.0.10)(react@19.0.0)':
1771 | dependencies:
1772 | '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1773 | react: 19.0.0
1774 | optionalDependencies:
1775 | '@types/react': 19.0.10
1776 |
1777 | '@radix-ui/react-popper@1.2.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1778 | dependencies:
1779 | '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1780 | '@radix-ui/react-arrow': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1781 | '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1782 | '@radix-ui/react-context': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1783 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1784 | '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1785 | '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1786 | '@radix-ui/react-use-rect': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1787 | '@radix-ui/react-use-size': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1788 | '@radix-ui/rect': 1.1.0
1789 | react: 19.0.0
1790 | react-dom: 19.0.0(react@19.0.0)
1791 | optionalDependencies:
1792 | '@types/react': 19.0.10
1793 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1794 |
1795 | '@radix-ui/react-portal@1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1796 | dependencies:
1797 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1798 | '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1799 | react: 19.0.0
1800 | react-dom: 19.0.0(react@19.0.0)
1801 | optionalDependencies:
1802 | '@types/react': 19.0.10
1803 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1804 |
1805 | '@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1806 | dependencies:
1807 | '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1808 | '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1809 | react: 19.0.0
1810 | react-dom: 19.0.0(react@19.0.0)
1811 | optionalDependencies:
1812 | '@types/react': 19.0.10
1813 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1814 |
1815 | '@radix-ui/react-primitive@2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1816 | dependencies:
1817 | '@radix-ui/react-slot': 1.1.2(@types/react@19.0.10)(react@19.0.0)
1818 | react: 19.0.0
1819 | react-dom: 19.0.0(react@19.0.0)
1820 | optionalDependencies:
1821 | '@types/react': 19.0.10
1822 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1823 |
1824 | '@radix-ui/react-slot@1.1.2(@types/react@19.0.10)(react@19.0.0)':
1825 | dependencies:
1826 | '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1827 | react: 19.0.0
1828 | optionalDependencies:
1829 | '@types/react': 19.0.10
1830 |
1831 | '@radix-ui/react-tooltip@1.1.8(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1832 | dependencies:
1833 | '@radix-ui/primitive': 1.1.1
1834 | '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1835 | '@radix-ui/react-context': 1.1.1(@types/react@19.0.10)(react@19.0.0)
1836 | '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1837 | '@radix-ui/react-id': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1838 | '@radix-ui/react-popper': 1.2.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1839 | '@radix-ui/react-portal': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1840 | '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1841 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1842 | '@radix-ui/react-slot': 1.1.2(@types/react@19.0.10)(react@19.0.0)
1843 | '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1844 | '@radix-ui/react-visually-hidden': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1845 | react: 19.0.0
1846 | react-dom: 19.0.0(react@19.0.0)
1847 | optionalDependencies:
1848 | '@types/react': 19.0.10
1849 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1850 |
1851 | '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.10)(react@19.0.0)':
1852 | dependencies:
1853 | react: 19.0.0
1854 | optionalDependencies:
1855 | '@types/react': 19.0.10
1856 |
1857 | '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.10)(react@19.0.0)':
1858 | dependencies:
1859 | '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1860 | react: 19.0.0
1861 | optionalDependencies:
1862 | '@types/react': 19.0.10
1863 |
1864 | '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.10)(react@19.0.0)':
1865 | dependencies:
1866 | '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1867 | react: 19.0.0
1868 | optionalDependencies:
1869 | '@types/react': 19.0.10
1870 |
1871 | '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.10)(react@19.0.0)':
1872 | dependencies:
1873 | react: 19.0.0
1874 | optionalDependencies:
1875 | '@types/react': 19.0.10
1876 |
1877 | '@radix-ui/react-use-rect@1.1.0(@types/react@19.0.10)(react@19.0.0)':
1878 | dependencies:
1879 | '@radix-ui/rect': 1.1.0
1880 | react: 19.0.0
1881 | optionalDependencies:
1882 | '@types/react': 19.0.10
1883 |
1884 | '@radix-ui/react-use-size@1.1.0(@types/react@19.0.10)(react@19.0.0)':
1885 | dependencies:
1886 | '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0)
1887 | react: 19.0.0
1888 | optionalDependencies:
1889 | '@types/react': 19.0.10
1890 |
1891 | '@radix-ui/react-visually-hidden@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
1892 | dependencies:
1893 | '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
1894 | react: 19.0.0
1895 | react-dom: 19.0.0(react@19.0.0)
1896 | optionalDependencies:
1897 | '@types/react': 19.0.10
1898 | '@types/react-dom': 19.0.4(@types/react@19.0.10)
1899 |
1900 | '@radix-ui/rect@1.1.0': {}
1901 |
1902 | '@swc/counter@0.1.3': {}
1903 |
1904 | '@swc/helpers@0.5.15':
1905 | dependencies:
1906 | tslib: 2.8.1
1907 |
1908 | '@tailwindcss/node@4.0.13':
1909 | dependencies:
1910 | enhanced-resolve: 5.18.1
1911 | jiti: 2.4.2
1912 | tailwindcss: 4.0.13
1913 |
1914 | '@tailwindcss/oxide-android-arm64@4.0.13':
1915 | optional: true
1916 |
1917 | '@tailwindcss/oxide-darwin-arm64@4.0.13':
1918 | optional: true
1919 |
1920 | '@tailwindcss/oxide-darwin-x64@4.0.13':
1921 | optional: true
1922 |
1923 | '@tailwindcss/oxide-freebsd-x64@4.0.13':
1924 | optional: true
1925 |
1926 | '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.13':
1927 | optional: true
1928 |
1929 | '@tailwindcss/oxide-linux-arm64-gnu@4.0.13':
1930 | optional: true
1931 |
1932 | '@tailwindcss/oxide-linux-arm64-musl@4.0.13':
1933 | optional: true
1934 |
1935 | '@tailwindcss/oxide-linux-x64-gnu@4.0.13':
1936 | optional: true
1937 |
1938 | '@tailwindcss/oxide-linux-x64-musl@4.0.13':
1939 | optional: true
1940 |
1941 | '@tailwindcss/oxide-win32-arm64-msvc@4.0.13':
1942 | optional: true
1943 |
1944 | '@tailwindcss/oxide-win32-x64-msvc@4.0.13':
1945 | optional: true
1946 |
1947 | '@tailwindcss/oxide@4.0.13':
1948 | optionalDependencies:
1949 | '@tailwindcss/oxide-android-arm64': 4.0.13
1950 | '@tailwindcss/oxide-darwin-arm64': 4.0.13
1951 | '@tailwindcss/oxide-darwin-x64': 4.0.13
1952 | '@tailwindcss/oxide-freebsd-x64': 4.0.13
1953 | '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.13
1954 | '@tailwindcss/oxide-linux-arm64-gnu': 4.0.13
1955 | '@tailwindcss/oxide-linux-arm64-musl': 4.0.13
1956 | '@tailwindcss/oxide-linux-x64-gnu': 4.0.13
1957 | '@tailwindcss/oxide-linux-x64-musl': 4.0.13
1958 | '@tailwindcss/oxide-win32-arm64-msvc': 4.0.13
1959 | '@tailwindcss/oxide-win32-x64-msvc': 4.0.13
1960 |
1961 | '@tailwindcss/postcss@4.0.13':
1962 | dependencies:
1963 | '@alloc/quick-lru': 5.2.0
1964 | '@tailwindcss/node': 4.0.13
1965 | '@tailwindcss/oxide': 4.0.13
1966 | lightningcss: 1.29.2
1967 | postcss: 8.4.49
1968 | tailwindcss: 4.0.13
1969 |
1970 | '@types/node@22.10.1':
1971 | dependencies:
1972 | undici-types: 6.20.0
1973 |
1974 | '@types/react-dom@19.0.4(@types/react@19.0.10)':
1975 | dependencies:
1976 | '@types/react': 19.0.10
1977 |
1978 | '@types/react@19.0.10':
1979 | dependencies:
1980 | csstype: 3.1.3
1981 |
1982 | aria-hidden@1.2.4:
1983 | dependencies:
1984 | tslib: 2.8.1
1985 |
1986 | buffer-from@1.1.2: {}
1987 |
1988 | busboy@1.6.0:
1989 | dependencies:
1990 | streamsearch: 1.1.0
1991 |
1992 | caniuse-lite@1.0.30001703: {}
1993 |
1994 | class-variance-authority@0.7.1:
1995 | dependencies:
1996 | clsx: 2.1.1
1997 |
1998 | client-only@0.0.1: {}
1999 |
2000 | clsx@2.1.1: {}
2001 |
2002 | color-convert@2.0.1:
2003 | dependencies:
2004 | color-name: 1.1.4
2005 | optional: true
2006 |
2007 | color-name@1.1.4:
2008 | optional: true
2009 |
2010 | color-string@1.9.1:
2011 | dependencies:
2012 | color-name: 1.1.4
2013 | simple-swizzle: 0.2.2
2014 | optional: true
2015 |
2016 | color@4.2.3:
2017 | dependencies:
2018 | color-convert: 2.0.1
2019 | color-string: 1.9.1
2020 | optional: true
2021 |
2022 | csstype@3.1.3: {}
2023 |
2024 | debug@4.3.7:
2025 | dependencies:
2026 | ms: 2.1.3
2027 |
2028 | detect-libc@2.0.3: {}
2029 |
2030 | detect-node-es@1.1.0: {}
2031 |
2032 | dotenv@16.4.5: {}
2033 |
2034 | drizzle-kit@0.28.1:
2035 | dependencies:
2036 | '@drizzle-team/brocli': 0.10.2
2037 | '@esbuild-kit/esm-loader': 2.6.5
2038 | esbuild: 0.19.12
2039 | esbuild-register: 3.6.0(esbuild@0.19.12)
2040 | transitivePeerDependencies:
2041 | - supports-color
2042 |
2043 | drizzle-orm@0.36.4(@types/react@19.0.10)(postgres@3.4.5)(react@19.0.0):
2044 | optionalDependencies:
2045 | '@types/react': 19.0.10
2046 | postgres: 3.4.5
2047 | react: 19.0.0
2048 |
2049 | enhanced-resolve@5.18.1:
2050 | dependencies:
2051 | graceful-fs: 4.2.11
2052 | tapable: 2.2.1
2053 |
2054 | esbuild-register@3.6.0(esbuild@0.19.12):
2055 | dependencies:
2056 | debug: 4.3.7
2057 | esbuild: 0.19.12
2058 | transitivePeerDependencies:
2059 | - supports-color
2060 |
2061 | esbuild@0.18.20:
2062 | optionalDependencies:
2063 | '@esbuild/android-arm': 0.18.20
2064 | '@esbuild/android-arm64': 0.18.20
2065 | '@esbuild/android-x64': 0.18.20
2066 | '@esbuild/darwin-arm64': 0.18.20
2067 | '@esbuild/darwin-x64': 0.18.20
2068 | '@esbuild/freebsd-arm64': 0.18.20
2069 | '@esbuild/freebsd-x64': 0.18.20
2070 | '@esbuild/linux-arm': 0.18.20
2071 | '@esbuild/linux-arm64': 0.18.20
2072 | '@esbuild/linux-ia32': 0.18.20
2073 | '@esbuild/linux-loong64': 0.18.20
2074 | '@esbuild/linux-mips64el': 0.18.20
2075 | '@esbuild/linux-ppc64': 0.18.20
2076 | '@esbuild/linux-riscv64': 0.18.20
2077 | '@esbuild/linux-s390x': 0.18.20
2078 | '@esbuild/linux-x64': 0.18.20
2079 | '@esbuild/netbsd-x64': 0.18.20
2080 | '@esbuild/openbsd-x64': 0.18.20
2081 | '@esbuild/sunos-x64': 0.18.20
2082 | '@esbuild/win32-arm64': 0.18.20
2083 | '@esbuild/win32-ia32': 0.18.20
2084 | '@esbuild/win32-x64': 0.18.20
2085 |
2086 | esbuild@0.19.12:
2087 | optionalDependencies:
2088 | '@esbuild/aix-ppc64': 0.19.12
2089 | '@esbuild/android-arm': 0.19.12
2090 | '@esbuild/android-arm64': 0.19.12
2091 | '@esbuild/android-x64': 0.19.12
2092 | '@esbuild/darwin-arm64': 0.19.12
2093 | '@esbuild/darwin-x64': 0.19.12
2094 | '@esbuild/freebsd-arm64': 0.19.12
2095 | '@esbuild/freebsd-x64': 0.19.12
2096 | '@esbuild/linux-arm': 0.19.12
2097 | '@esbuild/linux-arm64': 0.19.12
2098 | '@esbuild/linux-ia32': 0.19.12
2099 | '@esbuild/linux-loong64': 0.19.12
2100 | '@esbuild/linux-mips64el': 0.19.12
2101 | '@esbuild/linux-ppc64': 0.19.12
2102 | '@esbuild/linux-riscv64': 0.19.12
2103 | '@esbuild/linux-s390x': 0.19.12
2104 | '@esbuild/linux-x64': 0.19.12
2105 | '@esbuild/netbsd-x64': 0.19.12
2106 | '@esbuild/openbsd-x64': 0.19.12
2107 | '@esbuild/sunos-x64': 0.19.12
2108 | '@esbuild/win32-arm64': 0.19.12
2109 | '@esbuild/win32-ia32': 0.19.12
2110 | '@esbuild/win32-x64': 0.19.12
2111 |
2112 | geist@1.3.1(next@15.3.0-canary.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)):
2113 | dependencies:
2114 | next: 15.3.0-canary.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
2115 |
2116 | get-nonce@1.0.1: {}
2117 |
2118 | get-tsconfig@4.8.1:
2119 | dependencies:
2120 | resolve-pkg-maps: 1.0.0
2121 |
2122 | graceful-fs@4.2.11: {}
2123 |
2124 | is-arrayish@0.3.2:
2125 | optional: true
2126 |
2127 | jiti@2.4.2: {}
2128 |
2129 | lightningcss-darwin-arm64@1.29.2:
2130 | optional: true
2131 |
2132 | lightningcss-darwin-x64@1.29.2:
2133 | optional: true
2134 |
2135 | lightningcss-freebsd-x64@1.29.2:
2136 | optional: true
2137 |
2138 | lightningcss-linux-arm-gnueabihf@1.29.2:
2139 | optional: true
2140 |
2141 | lightningcss-linux-arm64-gnu@1.29.2:
2142 | optional: true
2143 |
2144 | lightningcss-linux-arm64-musl@1.29.2:
2145 | optional: true
2146 |
2147 | lightningcss-linux-x64-gnu@1.29.2:
2148 | optional: true
2149 |
2150 | lightningcss-linux-x64-musl@1.29.2:
2151 | optional: true
2152 |
2153 | lightningcss-win32-arm64-msvc@1.29.2:
2154 | optional: true
2155 |
2156 | lightningcss-win32-x64-msvc@1.29.2:
2157 | optional: true
2158 |
2159 | lightningcss@1.29.2:
2160 | dependencies:
2161 | detect-libc: 2.0.3
2162 | optionalDependencies:
2163 | lightningcss-darwin-arm64: 1.29.2
2164 | lightningcss-darwin-x64: 1.29.2
2165 | lightningcss-freebsd-x64: 1.29.2
2166 | lightningcss-linux-arm-gnueabihf: 1.29.2
2167 | lightningcss-linux-arm64-gnu: 1.29.2
2168 | lightningcss-linux-arm64-musl: 1.29.2
2169 | lightningcss-linux-x64-gnu: 1.29.2
2170 | lightningcss-linux-x64-musl: 1.29.2
2171 | lightningcss-win32-arm64-msvc: 1.29.2
2172 | lightningcss-win32-x64-msvc: 1.29.2
2173 |
2174 | lucide-react@0.462.0(react@19.0.0):
2175 | dependencies:
2176 | react: 19.0.0
2177 |
2178 | ms@2.1.3: {}
2179 |
2180 | nanoid@3.3.8: {}
2181 |
2182 | nanoid@3.3.9: {}
2183 |
2184 | next@15.3.0-canary.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
2185 | dependencies:
2186 | '@next/env': 15.3.0-canary.0
2187 | '@swc/counter': 0.1.3
2188 | '@swc/helpers': 0.5.15
2189 | busboy: 1.6.0
2190 | caniuse-lite: 1.0.30001703
2191 | postcss: 8.4.31
2192 | react: 19.0.0
2193 | react-dom: 19.0.0(react@19.0.0)
2194 | styled-jsx: 5.1.6(react@19.0.0)
2195 | optionalDependencies:
2196 | '@next/swc-darwin-arm64': 15.3.0-canary.0
2197 | '@next/swc-darwin-x64': 15.3.0-canary.0
2198 | '@next/swc-linux-arm64-gnu': 15.3.0-canary.0
2199 | '@next/swc-linux-arm64-musl': 15.3.0-canary.0
2200 | '@next/swc-linux-x64-gnu': 15.3.0-canary.0
2201 | '@next/swc-linux-x64-musl': 15.3.0-canary.0
2202 | '@next/swc-win32-arm64-msvc': 15.3.0-canary.0
2203 | '@next/swc-win32-x64-msvc': 15.3.0-canary.0
2204 | sharp: 0.33.5
2205 | transitivePeerDependencies:
2206 | - '@babel/core'
2207 | - babel-plugin-macros
2208 |
2209 | picocolors@1.1.1: {}
2210 |
2211 | postcss@8.4.31:
2212 | dependencies:
2213 | nanoid: 3.3.9
2214 | picocolors: 1.1.1
2215 | source-map-js: 1.2.1
2216 |
2217 | postcss@8.4.49:
2218 | dependencies:
2219 | nanoid: 3.3.8
2220 | picocolors: 1.1.1
2221 | source-map-js: 1.2.1
2222 |
2223 | postgres@3.4.5: {}
2224 |
2225 | prettier-plugin-organize-imports@4.1.0(prettier@3.5.3)(typescript@5.8.2):
2226 | dependencies:
2227 | prettier: 3.5.3
2228 | typescript: 5.8.2
2229 |
2230 | prettier-plugin-tailwindcss@0.6.11(prettier-plugin-organize-imports@4.1.0(prettier@3.5.3)(typescript@5.8.2))(prettier@3.5.3):
2231 | dependencies:
2232 | prettier: 3.5.3
2233 | optionalDependencies:
2234 | prettier-plugin-organize-imports: 4.1.0(prettier@3.5.3)(typescript@5.8.2)
2235 |
2236 | prettier@3.5.3: {}
2237 |
2238 | react-dom@19.0.0(react@19.0.0):
2239 | dependencies:
2240 | react: 19.0.0
2241 | scheduler: 0.25.0
2242 |
2243 | react-remove-scroll-bar@2.3.8(@types/react@19.0.10)(react@19.0.0):
2244 | dependencies:
2245 | react: 19.0.0
2246 | react-style-singleton: 2.2.3(@types/react@19.0.10)(react@19.0.0)
2247 | tslib: 2.8.1
2248 | optionalDependencies:
2249 | '@types/react': 19.0.10
2250 |
2251 | react-remove-scroll@2.6.3(@types/react@19.0.10)(react@19.0.0):
2252 | dependencies:
2253 | react: 19.0.0
2254 | react-remove-scroll-bar: 2.3.8(@types/react@19.0.10)(react@19.0.0)
2255 | react-style-singleton: 2.2.3(@types/react@19.0.10)(react@19.0.0)
2256 | tslib: 2.8.1
2257 | use-callback-ref: 1.3.3(@types/react@19.0.10)(react@19.0.0)
2258 | use-sidecar: 1.1.3(@types/react@19.0.10)(react@19.0.0)
2259 | optionalDependencies:
2260 | '@types/react': 19.0.10
2261 |
2262 | react-style-singleton@2.2.3(@types/react@19.0.10)(react@19.0.0):
2263 | dependencies:
2264 | get-nonce: 1.0.1
2265 | react: 19.0.0
2266 | tslib: 2.8.1
2267 | optionalDependencies:
2268 | '@types/react': 19.0.10
2269 |
2270 | react@19.0.0: {}
2271 |
2272 | resolve-pkg-maps@1.0.0: {}
2273 |
2274 | scheduler@0.25.0: {}
2275 |
2276 | semver@7.7.1:
2277 | optional: true
2278 |
2279 | sharp@0.33.5:
2280 | dependencies:
2281 | color: 4.2.3
2282 | detect-libc: 2.0.3
2283 | semver: 7.7.1
2284 | optionalDependencies:
2285 | '@img/sharp-darwin-arm64': 0.33.5
2286 | '@img/sharp-darwin-x64': 0.33.5
2287 | '@img/sharp-libvips-darwin-arm64': 1.0.4
2288 | '@img/sharp-libvips-darwin-x64': 1.0.4
2289 | '@img/sharp-libvips-linux-arm': 1.0.5
2290 | '@img/sharp-libvips-linux-arm64': 1.0.4
2291 | '@img/sharp-libvips-linux-s390x': 1.0.4
2292 | '@img/sharp-libvips-linux-x64': 1.0.4
2293 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
2294 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4
2295 | '@img/sharp-linux-arm': 0.33.5
2296 | '@img/sharp-linux-arm64': 0.33.5
2297 | '@img/sharp-linux-s390x': 0.33.5
2298 | '@img/sharp-linux-x64': 0.33.5
2299 | '@img/sharp-linuxmusl-arm64': 0.33.5
2300 | '@img/sharp-linuxmusl-x64': 0.33.5
2301 | '@img/sharp-wasm32': 0.33.5
2302 | '@img/sharp-win32-ia32': 0.33.5
2303 | '@img/sharp-win32-x64': 0.33.5
2304 | optional: true
2305 |
2306 | simple-swizzle@0.2.2:
2307 | dependencies:
2308 | is-arrayish: 0.3.2
2309 | optional: true
2310 |
2311 | sonner@1.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
2312 | dependencies:
2313 | react: 19.0.0
2314 | react-dom: 19.0.0(react@19.0.0)
2315 |
2316 | source-map-js@1.2.1: {}
2317 |
2318 | source-map-support@0.5.21:
2319 | dependencies:
2320 | buffer-from: 1.1.2
2321 | source-map: 0.6.1
2322 |
2323 | source-map@0.6.1: {}
2324 |
2325 | streamsearch@1.1.0: {}
2326 |
2327 | styled-jsx@5.1.6(react@19.0.0):
2328 | dependencies:
2329 | client-only: 0.0.1
2330 | react: 19.0.0
2331 |
2332 | tailwind-merge@3.0.2: {}
2333 |
2334 | tailwindcss-animate@1.0.7(tailwindcss@4.0.13):
2335 | dependencies:
2336 | tailwindcss: 4.0.13
2337 |
2338 | tailwindcss@4.0.13: {}
2339 |
2340 | tapable@2.2.1: {}
2341 |
2342 | tslib@2.8.1: {}
2343 |
2344 | typescript@5.8.2: {}
2345 |
2346 | undici-types@6.20.0: {}
2347 |
2348 | use-callback-ref@1.3.3(@types/react@19.0.10)(react@19.0.0):
2349 | dependencies:
2350 | react: 19.0.0
2351 | tslib: 2.8.1
2352 | optionalDependencies:
2353 | '@types/react': 19.0.10
2354 |
2355 | use-sidecar@1.1.3(@types/react@19.0.10)(react@19.0.0):
2356 | dependencies:
2357 | detect-node-es: 1.1.0
2358 | react: 19.0.0
2359 | tslib: 2.8.1
2360 | optionalDependencies:
2361 | '@types/react': 19.0.10
2362 |
2363 | zod@3.23.8: {}
2364 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | '@tailwindcss/postcss': {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/public/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/linkedin.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/public/placeholder.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/x.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "baseUrl": ".",
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------