diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8a85ba7 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Copy from .env.local on the Vercel dashboard +# https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database +POSTGRES_URL= +POSTGRES_PRISMA_URL= +POSTGRES_URL_NON_POOLING= +POSTGRES_USER= +POSTGRES_HOST= +POSTGRES_PASSWORD= +POSTGRES_DATABASE= + +# `openssl rand -base64 32` +AUTH_SECRET= +AUTH_URL=http://localhost:3000/api/auth \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45c1abc --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md index 16aad0b..aaf0dfe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ +<<<<<<< HEAD # Next.jsReactDashbord-maryam selflearning -Next.js \ No newline at end of file +Next.js +======= +## Next.js App Router Course - Starter + +This is the starter template for the Next.js App Router Course. It contains the starting code for the dashboard application. + +For more information, see the [course curriculum](https://nextjs.org/learn) on the Next.js Website. +>>>>>>> 951306d (Initial commit with all project files) diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..53f3182 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,14 @@ +import '@/app/ui/global.css'; +import { inter } from '@/app/ui/fonts'; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/app/lib/data.ts b/app/lib/data.ts new file mode 100644 index 0000000..d9bba04 --- /dev/null +++ b/app/lib/data.ts @@ -0,0 +1,218 @@ +import postgres from 'postgres'; +import { + CustomerField, + CustomersTableType, + InvoiceForm, + InvoicesTable, + LatestInvoiceRaw, + Revenue, +} from './definitions'; +import { formatCurrency } from './utils'; + +const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); + +export async function fetchRevenue() { + try { + // Artificially delay a response for demo purposes. + // Don't do this in production :) + + // console.log('Fetching revenue data...'); + // await new Promise((resolve) => setTimeout(resolve, 3000)); + + const data = await sql`SELECT * FROM revenue`; + + // console.log('Data fetch completed after 3 seconds.'); + + return data; + } catch (error) { + console.error('Database Error:', error); + throw new Error('Failed to fetch revenue data.'); + } +} + +export async function fetchLatestInvoices() { + try { + const data = await sql` + SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id + FROM invoices + JOIN customers ON invoices.customer_id = customers.id + ORDER BY invoices.date DESC + LIMIT 5`; + + const latestInvoices = data.map((invoice) => ({ + ...invoice, + amount: formatCurrency(invoice.amount), + })); + return latestInvoices; + } catch (error) { + console.error('Database Error:', error); + throw new Error('Failed to fetch the latest invoices.'); + } +} + +export async function fetchCardData() { + try { + // You can probably combine these into a single SQL query + // However, we are intentionally splitting them to demonstrate + // how to initialize multiple queries in parallel with JS. + const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`; + const customerCountPromise = sql`SELECT COUNT(*) FROM customers`; + const invoiceStatusPromise = sql`SELECT + SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid", + SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending" + FROM invoices`; + + const data = await Promise.all([ + invoiceCountPromise, + customerCountPromise, + invoiceStatusPromise, + ]); + + const numberOfInvoices = Number(data[0][0].count ?? '0'); + const numberOfCustomers = Number(data[1][0].count ?? '0'); + const totalPaidInvoices = formatCurrency(data[2][0].paid ?? '0'); + const totalPendingInvoices = formatCurrency(data[2][0].pending ?? '0'); + + return { + numberOfCustomers, + numberOfInvoices, + totalPaidInvoices, + totalPendingInvoices, + }; + } catch (error) { + console.error('Database Error:', error); + throw new Error('Failed to fetch card data.'); + } +} + +const ITEMS_PER_PAGE = 6; +export async function fetchFilteredInvoices( + query: string, + currentPage: number, +) { + const offset = (currentPage - 1) * ITEMS_PER_PAGE; + + try { + const invoices = await sql` + SELECT + invoices.id, + invoices.amount, + invoices.date, + invoices.status, + customers.name, + customers.email, + customers.image_url + FROM invoices + JOIN customers ON invoices.customer_id = customers.id + WHERE + customers.name ILIKE ${`%${query}%`} OR + customers.email ILIKE ${`%${query}%`} OR + invoices.amount::text ILIKE ${`%${query}%`} OR + invoices.date::text ILIKE ${`%${query}%`} OR + invoices.status ILIKE ${`%${query}%`} + ORDER BY invoices.date DESC + LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset} + `; + + return invoices; + } catch (error) { + console.error('Database Error:', error); + throw new Error('Failed to fetch invoices.'); + } +} + +export async function fetchInvoicesPages(query: string) { + try { + const data = await sql`SELECT COUNT(*) + FROM invoices + JOIN customers ON invoices.customer_id = customers.id + WHERE + customers.name ILIKE ${`%${query}%`} OR + customers.email ILIKE ${`%${query}%`} OR + invoices.amount::text ILIKE ${`%${query}%`} OR + invoices.date::text ILIKE ${`%${query}%`} OR + invoices.status ILIKE ${`%${query}%`} + `; + + const totalPages = Math.ceil(Number(data[0].count) / ITEMS_PER_PAGE); + return totalPages; + } catch (error) { + console.error('Database Error:', error); + throw new Error('Failed to fetch total number of invoices.'); + } +} + +export async function fetchInvoiceById(id: string) { + try { + const data = await sql` + SELECT + invoices.id, + invoices.customer_id, + invoices.amount, + invoices.status + FROM invoices + WHERE invoices.id = ${id}; + `; + + const invoice = data.map((invoice) => ({ + ...invoice, + // Convert amount from cents to dollars + amount: invoice.amount / 100, + })); + + return invoice[0]; + } catch (error) { + console.error('Database Error:', error); + throw new Error('Failed to fetch invoice.'); + } +} + +export async function fetchCustomers() { + try { + const customers = await sql` + SELECT + id, + name + FROM customers + ORDER BY name ASC + `; + + return customers; + } catch (err) { + console.error('Database Error:', err); + throw new Error('Failed to fetch all customers.'); + } +} + +export async function fetchFilteredCustomers(query: string) { + try { + const data = await sql` + SELECT + customers.id, + customers.name, + customers.email, + customers.image_url, + COUNT(invoices.id) AS total_invoices, + SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending, + SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid + FROM customers + LEFT JOIN invoices ON customers.id = invoices.customer_id + WHERE + customers.name ILIKE ${`%${query}%`} OR + customers.email ILIKE ${`%${query}%`} + GROUP BY customers.id, customers.name, customers.email, customers.image_url + ORDER BY customers.name ASC + `; + + const customers = data.map((customer) => ({ + ...customer, + total_pending: formatCurrency(customer.total_pending), + total_paid: formatCurrency(customer.total_paid), + })); + + return customers; + } catch (err) { + console.error('Database Error:', err); + throw new Error('Failed to fetch customer table.'); + } +} diff --git a/app/lib/definitions.ts b/app/lib/definitions.ts new file mode 100644 index 0000000..0f4c19a --- /dev/null +++ b/app/lib/definitions.ts @@ -0,0 +1,9 @@ +export type Invoice = { + id: string; + customer_id: string; + amount: number; + date: string; + // In TypeScript, this is called a string union type. + // It means that the "status" property can only be one of the two strings: 'pending' or 'paid'. + status: 'pending' | 'paid'; +}; \ No newline at end of file diff --git a/app/lib/placeholder-data.ts b/app/lib/placeholder-data.ts new file mode 100644 index 0000000..f93e689 --- /dev/null +++ b/app/lib/placeholder-data.ts @@ -0,0 +1,15 @@ +const invoices = [ + { + customer_id: customers[0].id, + amount: 15795, + status: 'pending', + date: '2022-12-06', + }, + { + customer_id: customers[1].id, + amount: 20348, + status: 'pending', + date: '2022-11-14', + }, + // ... +]; \ No newline at end of file diff --git a/app/lib/utils.ts b/app/lib/utils.ts new file mode 100644 index 0000000..b7f7cff --- /dev/null +++ b/app/lib/utils.ts @@ -0,0 +1,69 @@ +import { Revenue } from './definitions'; + +export const formatCurrency = (amount: number) => { + return (amount / 100).toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + }); +}; + +export const formatDateToLocal = ( + dateStr: string, + locale: string = 'en-US', +) => { + const date = new Date(dateStr); + const options: Intl.DateTimeFormatOptions = { + day: 'numeric', + month: 'short', + year: 'numeric', + }; + const formatter = new Intl.DateTimeFormat(locale, options); + return formatter.format(date); +}; + +export const generateYAxis = (revenue: Revenue[]) => { + // Calculate what labels we need to display on the y-axis + // based on highest record and in 1000s + const yAxisLabels = []; + const highestRecord = Math.max(...revenue.map((month) => month.revenue)); + const topLabel = Math.ceil(highestRecord / 1000) * 1000; + + for (let i = topLabel; i >= 0; i -= 1000) { + yAxisLabels.push(`$${i / 1000}K`); + } + + return { yAxisLabels, topLabel }; +}; + +export const generatePagination = (currentPage: number, totalPages: number) => { + // If the total number of pages is 7 or less, + // display all pages without any ellipsis. + if (totalPages <= 7) { + return Array.from({ length: totalPages }, (_, i) => i + 1); + } + + // If the current page is among the first 3 pages, + // show the first 3, an ellipsis, and the last 2 pages. + if (currentPage <= 3) { + return [1, 2, 3, '...', totalPages - 1, totalPages]; + } + + // If the current page is among the last 3 pages, + // show the first 2, an ellipsis, and the last 3 pages. + if (currentPage >= totalPages - 2) { + return [1, 2, '...', totalPages - 2, totalPages - 1, totalPages]; + } + + // If the current page is somewhere in the middle, + // show the first page, an ellipsis, the current page and its neighbors, + // another ellipsis, and the last page. + return [ + 1, + '...', + currentPage - 1, + currentPage, + currentPage + 1, + '...', + totalPages, + ]; +}; diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..5227cc0 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,50 @@ +import AcmeLogo from '@/app/ui/acme-logo'; +import { ArrowRightIcon } from '@heroicons/react/24/outline'; +import Link from 'next/link'; +import styles from '@/app/ui/home.module.css'; +import Image from 'next/image'; + +export default function Page() { + return ( +
+
+
+ + {/* */} +
+
+
+
+

+ Welcome to Acme. This is the example for the{' '} + + Next.js Learn Course + + , brought to you by Vercel. +

+ + Log in + +
+
+ {/* Add Hero Images Here */ + + Screenshots of the dashboard project showing desktop version + } + +
+
+
+ ); +} diff --git a/app/query/route.ts b/app/query/route.ts new file mode 100644 index 0000000..0701b73 --- /dev/null +++ b/app/query/route.ts @@ -0,0 +1,26 @@ +// import postgres from 'postgres'; + +// const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); + +// async function listInvoices() { +// const data = await sql` +// SELECT invoices.amount, customers.name +// FROM invoices +// JOIN customers ON invoices.customer_id = customers.id +// WHERE invoices.amount = 666; +// `; + +// return data; +// } + +export async function GET() { + return Response.json({ + message: + 'Uncomment this file and remove this line. You can delete this file when you are finished.', + }); + // try { + // return Response.json(await listInvoices()); + // } catch (error) { + // return Response.json({ error }, { status: 500 }); + // } +} diff --git a/app/seed/route.ts b/app/seed/route.ts new file mode 100644 index 0000000..c6428b2 --- /dev/null +++ b/app/seed/route.ts @@ -0,0 +1,117 @@ +import bcrypt from 'bcrypt'; +import postgres from 'postgres'; +import { invoices, customers, revenue, users } from '../lib/placeholder-data'; + +const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); + +async function seedUsers() { + await sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`; + await sql` + CREATE TABLE IF NOT EXISTS users ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email TEXT NOT NULL UNIQUE, + password TEXT NOT NULL + ); + `; + + const insertedUsers = await Promise.all( + users.map(async (user) => { + const hashedPassword = await bcrypt.hash(user.password, 10); + return sql` + INSERT INTO users (id, name, email, password) + VALUES (${user.id}, ${user.name}, ${user.email}, ${hashedPassword}) + ON CONFLICT (id) DO NOTHING; + `; + }), + ); + + return insertedUsers; +} + +async function seedInvoices() { + await sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`; + + await sql` + CREATE TABLE IF NOT EXISTS invoices ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + customer_id UUID NOT NULL, + amount INT NOT NULL, + status VARCHAR(255) NOT NULL, + date DATE NOT NULL + ); + `; + + const insertedInvoices = await Promise.all( + invoices.map( + (invoice) => sql` + INSERT INTO invoices (customer_id, amount, status, date) + VALUES (${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date}) + ON CONFLICT (id) DO NOTHING; + `, + ), + ); + + return insertedInvoices; +} + +async function seedCustomers() { + await sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`; + + await sql` + CREATE TABLE IF NOT EXISTS customers ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + image_url VARCHAR(255) NOT NULL + ); + `; + + const insertedCustomers = await Promise.all( + customers.map( + (customer) => sql` + INSERT INTO customers (id, name, email, image_url) + VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url}) + ON CONFLICT (id) DO NOTHING; + `, + ), + ); + + return insertedCustomers; +} + +async function seedRevenue() { + await sql` + CREATE TABLE IF NOT EXISTS revenue ( + month VARCHAR(4) NOT NULL UNIQUE, + revenue INT NOT NULL + ); + `; + + const insertedRevenue = await Promise.all( + revenue.map( + (rev) => sql` + INSERT INTO revenue (month, revenue) + VALUES (${rev.month}, ${rev.revenue}) + ON CONFLICT (month) DO NOTHING; + `, + ), + ); + + return insertedRevenue; +} + +export async function GET() { + try { + const result = await sql.begin((sql) => [ + seedUsers(), + seedCustomers(), + seedInvoices(), + seedRevenue(), + ]); + + return Response.json({ message: 'Database seeded successfully' }); + } catch (error) { + return Response.json({ error }, { status: 500 }); + } +} diff --git a/app/ui/acme-logo.tsx b/app/ui/acme-logo.tsx new file mode 100644 index 0000000..2ff23c5 --- /dev/null +++ b/app/ui/acme-logo.tsx @@ -0,0 +1,12 @@ +import { GlobeAltIcon } from '@heroicons/react/24/outline'; +import { inter } from '@/app/ui/fonts'; +export default function AcmeLogo() { + return ( +
+ +

Acme

+
+ ); +} diff --git a/app/ui/button.tsx b/app/ui/button.tsx new file mode 100644 index 0000000..af8f627 --- /dev/null +++ b/app/ui/button.tsx @@ -0,0 +1,19 @@ +import clsx from 'clsx'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + children: React.ReactNode; +} + +export function Button({ children, className, ...rest }: ButtonProps) { + return ( + + ); +} diff --git a/app/ui/customers/table.tsx b/app/ui/customers/table.tsx new file mode 100644 index 0000000..fce2f55 --- /dev/null +++ b/app/ui/customers/table.tsx @@ -0,0 +1,123 @@ +import Image from 'next/image'; +import { lusitana } from '@/app/ui/fonts'; +import Search from '@/app/ui/search'; +import { + CustomersTableType, + FormattedCustomersTable, +} from '@/app/lib/definitions'; + +export default async function CustomersTable({ + customers, +}: { + customers: FormattedCustomersTable[]; +}) { + return ( +
+

+ Customers +

+ +
+
+
+
+
+ {customers?.map((customer) => ( +
+
+
+
+
+ {`${customer.name}'s +

{customer.name}

+
+
+

+ {customer.email} +

+
+
+
+
+

Pending

+

{customer.total_pending}

+
+
+

Paid

+

{customer.total_paid}

+
+
+
+

{customer.total_invoices} invoices

+
+
+ ))} +
+ + + + + + + + + + + + + {customers.map((customer) => ( + + + + + + + + ))} + +
+ Name + + Email + + Total Invoices + + Total Pending + + Total Paid +
+
+ {`${customer.name}'s +

{customer.name}

+
+
+ {customer.email} + + {customer.total_invoices} + + {customer.total_pending} + + {customer.total_paid} +
+
+
+
+
+
+ ); +} diff --git a/app/ui/dashboard/cards.tsx b/app/ui/dashboard/cards.tsx new file mode 100644 index 0000000..526e6f9 --- /dev/null +++ b/app/ui/dashboard/cards.tsx @@ -0,0 +1,58 @@ +import { + BanknotesIcon, + ClockIcon, + UserGroupIcon, + InboxIcon, +} from '@heroicons/react/24/outline'; +import { lusitana } from '@/app/ui/fonts'; + +const iconMap = { + collected: BanknotesIcon, + customers: UserGroupIcon, + pending: ClockIcon, + invoices: InboxIcon, +}; + +export default async function CardWrapper() { + return ( + <> + {/* NOTE: Uncomment this code in Chapter 9 */} + + {/* + + + */} + + ); +} + +export function Card({ + title, + value, + type, +}: { + title: string; + value: number | string; + type: 'invoices' | 'customers' | 'pending' | 'collected'; +}) { + const Icon = iconMap[type]; + + return ( +
+
+ {Icon ? : null} +

{title}

+
+

+ {value} +

+
+ ); +} diff --git a/app/ui/dashboard/latest-invoices.tsx b/app/ui/dashboard/latest-invoices.tsx new file mode 100644 index 0000000..27b74f6 --- /dev/null +++ b/app/ui/dashboard/latest-invoices.tsx @@ -0,0 +1,64 @@ +import { ArrowPathIcon } from '@heroicons/react/24/outline'; +import clsx from 'clsx'; +import Image from 'next/image'; +import { lusitana } from '@/app/ui/fonts'; +import { LatestInvoice } from '@/app/lib/definitions'; +export default async function LatestInvoices({ + latestInvoices, +}: { + latestInvoices: LatestInvoice[]; +}) { + return ( +
+

+ Latest Invoices +

+
+ {/* NOTE: Uncomment this code in Chapter 7 */} + + {/*
+ {latestInvoices.map((invoice, i) => { + return ( +
+
+ {`${invoice.name}'s +
+

+ {invoice.name} +

+

+ {invoice.email} +

+
+
+

+ {invoice.amount} +

+
+ ); + })} +
*/} +
+ +

Updated just now

+
+
+
+ ); +} diff --git a/app/ui/dashboard/layout.tsx b/app/ui/dashboard/layout.tsx new file mode 100644 index 0000000..79bd4c3 --- /dev/null +++ b/app/ui/dashboard/layout.tsx @@ -0,0 +1,12 @@ +import SideNav from '@/app/ui/dashboard/sidenav'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+
+ +
+
{children}
+
+ ); +} \ No newline at end of file diff --git a/app/ui/dashboard/nav-links.tsx b/app/ui/dashboard/nav-links.tsx new file mode 100644 index 0000000..fc47aa6 --- /dev/null +++ b/app/ui/dashboard/nav-links.tsx @@ -0,0 +1,54 @@ +'use client'; +import { + UserGroupIcon, + HomeIcon, + DocumentDuplicateIcon, +} from '@heroicons/react/24/outline'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import clsx from 'clsx'; + + +// Map of links to display in the side navigation. +// Depending on the size of the application, this would be stored in a database. +const links = [ + { name: 'Home', href: '/dashboard', icon: HomeIcon }, + { + name: 'Invoices', + href: '/dashboard/invoices', + icon: DocumentDuplicateIcon, + }, + { name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon }, +]; + +export default function NavLinks() { + const pathname = usePathname(); + return ( + <> + {links.map((link) => { + const LinkIcon = link.icon; + return ( + + +

{link.name}

+ + ); + })} + + ); +} diff --git a/app/ui/dashboard/revenue-chart.tsx b/app/ui/dashboard/revenue-chart.tsx new file mode 100644 index 0000000..f19e698 --- /dev/null +++ b/app/ui/dashboard/revenue-chart.tsx @@ -0,0 +1,65 @@ +import { generateYAxis } from '@/app/lib/utils'; +import { CalendarIcon } from '@heroicons/react/24/outline'; +import { lusitana } from '@/app/ui/fonts'; +import { Revenue } from '@/app/lib/definitions'; + +// This component is representational only. +// For data visualization UI, check out: +// https://www.tremor.so/ +// https://www.chartjs.org/ +// https://airbnb.io/visx/ + +export default async function RevenueChart({ + revenue, +}: { + revenue: Revenue[]; +}) { + const chartHeight = 350; + // NOTE: Uncomment this code in Chapter 7 + + // const { yAxisLabels, topLabel } = generateYAxis(revenue); + + // if (!revenue || revenue.length === 0) { + // return

No data available.

; + // } + + return ( +
+

+ Recent Revenue +

+ {/* NOTE: Uncomment this code in Chapter 7 */} + + {/*
+
+
+ {yAxisLabels.map((label) => ( +

{label}

+ ))} +
+ + {revenue.map((month) => ( +
+
+

+ {month.month} +

+
+ ))} +
+
+ +

Last 12 months

+
+
*/} +
+ ); +} diff --git a/app/ui/dashboard/sidenav.tsx b/app/ui/dashboard/sidenav.tsx new file mode 100644 index 0000000..3d55b46 --- /dev/null +++ b/app/ui/dashboard/sidenav.tsx @@ -0,0 +1,29 @@ +import Link from 'next/link'; +import NavLinks from '@/app/ui/dashboard/nav-links'; +import AcmeLogo from '@/app/ui/acme-logo'; +import { PowerIcon } from '@heroicons/react/24/outline'; + +export default function SideNav() { + return ( +
+ +
+ +
+ +
+ +
+
+ +
+
+
+ ); +} diff --git a/app/ui/fonts.ts b/app/ui/fonts.ts new file mode 100644 index 0000000..4b9a491 --- /dev/null +++ b/app/ui/fonts.ts @@ -0,0 +1,3 @@ +import { Inter } from 'next/font/google'; + +export const inter = Inter({ subsets: ['latin'] }); diff --git a/app/ui/global.css b/app/ui/global.css new file mode 100644 index 0000000..c06d6d6 --- /dev/null +++ b/app/ui/global.css @@ -0,0 +1,18 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +input[type='number'] { + -moz-appearance: textfield; + appearance: textfield; +} + +input[type='number']::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type='number']::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} diff --git a/app/ui/home.module.css b/app/ui/home.module.css new file mode 100644 index 0000000..9e584f9 --- /dev/null +++ b/app/ui/home.module.css @@ -0,0 +1,7 @@ +.shape { + height: 0; + width: 0; + border-bottom: 30px solid black; + border-left: 20px solid transparent; + border-right: 20px solid transparent; +} \ No newline at end of file diff --git a/app/ui/invoices/breadcrumbs.tsx b/app/ui/invoices/breadcrumbs.tsx new file mode 100644 index 0000000..cd9e226 --- /dev/null +++ b/app/ui/invoices/breadcrumbs.tsx @@ -0,0 +1,36 @@ +import { clsx } from 'clsx'; +import Link from 'next/link'; +import { lusitana } from '@/app/ui/fonts'; + +interface Breadcrumb { + label: string; + href: string; + active?: boolean; +} + +export default function Breadcrumbs({ + breadcrumbs, +}: { + breadcrumbs: Breadcrumb[]; +}) { + return ( + + ); +} diff --git a/app/ui/invoices/buttons.tsx b/app/ui/invoices/buttons.tsx new file mode 100644 index 0000000..27bff1f --- /dev/null +++ b/app/ui/invoices/buttons.tsx @@ -0,0 +1,36 @@ +import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; +import Link from 'next/link'; + +export function CreateInvoice() { + return ( + + Create Invoice{' '} + + + ); +} + +export function UpdateInvoice({ id }: { id: string }) { + return ( + + + + ); +} + +export function DeleteInvoice({ id }: { id: string }) { + return ( + <> + + + ); +} diff --git a/app/ui/invoices/create-form.tsx b/app/ui/invoices/create-form.tsx new file mode 100644 index 0000000..35099ce --- /dev/null +++ b/app/ui/invoices/create-form.tsx @@ -0,0 +1,112 @@ +import { CustomerField } from '@/app/lib/definitions'; +import Link from 'next/link'; +import { + CheckIcon, + ClockIcon, + CurrencyDollarIcon, + UserCircleIcon, +} from '@heroicons/react/24/outline'; +import { Button } from '@/app/ui/button'; + +export default function Form({ customers }: { customers: CustomerField[] }) { + return ( +
+
+ {/* Customer Name */} +
+ +
+ + +
+
+ + {/* Invoice Amount */} +
+ +
+
+ + +
+
+
+ + {/* Invoice Status */} +
+ + Set the invoice status + +
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + Cancel + + +
+
+ ); +} diff --git a/app/ui/invoices/edit-form.tsx b/app/ui/invoices/edit-form.tsx new file mode 100644 index 0000000..8673667 --- /dev/null +++ b/app/ui/invoices/edit-form.tsx @@ -0,0 +1,123 @@ +'use client'; + +import { CustomerField, InvoiceForm } from '@/app/lib/definitions'; +import { + CheckIcon, + ClockIcon, + CurrencyDollarIcon, + UserCircleIcon, +} from '@heroicons/react/24/outline'; +import Link from 'next/link'; +import { Button } from '@/app/ui/button'; + +export default function EditInvoiceForm({ + invoice, + customers, +}: { + invoice: InvoiceForm; + customers: CustomerField[]; +}) { + return ( +
+
+ {/* Customer Name */} +
+ +
+ + +
+
+ + {/* Invoice Amount */} +
+ +
+
+ + +
+
+
+ + {/* Invoice Status */} +
+ + Set the invoice status + +
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + Cancel + + +
+
+ ); +} diff --git a/app/ui/invoices/pagination.tsx b/app/ui/invoices/pagination.tsx new file mode 100644 index 0000000..1113a29 --- /dev/null +++ b/app/ui/invoices/pagination.tsx @@ -0,0 +1,119 @@ +'use client'; + +import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline'; +import clsx from 'clsx'; +import Link from 'next/link'; +import { generatePagination } from '@/app/lib/utils'; + +export default function Pagination({ totalPages }: { totalPages: number }) { + // NOTE: Uncomment this code in Chapter 11 + + // const allPages = generatePagination(currentPage, totalPages); + + return ( + <> + {/* NOTE: Uncomment this code in Chapter 11 */} + + {/*
+ + +
+ {allPages.map((page, index) => { + let position: 'first' | 'last' | 'single' | 'middle' | undefined; + + if (index === 0) position = 'first'; + if (index === allPages.length - 1) position = 'last'; + if (allPages.length === 1) position = 'single'; + if (page === '...') position = 'middle'; + + return ( + + ); + })} +
+ + = totalPages} + /> +
*/} + + ); +} + +function PaginationNumber({ + page, + href, + isActive, + position, +}: { + page: number | string; + href: string; + position?: 'first' | 'last' | 'middle' | 'single'; + isActive: boolean; +}) { + const className = clsx( + 'flex h-10 w-10 items-center justify-center text-sm border', + { + 'rounded-l-md': position === 'first' || position === 'single', + 'rounded-r-md': position === 'last' || position === 'single', + 'z-10 bg-blue-600 border-blue-600 text-white': isActive, + 'hover:bg-gray-100': !isActive && position !== 'middle', + 'text-gray-300': position === 'middle', + }, + ); + + return isActive || position === 'middle' ? ( +
{page}
+ ) : ( + + {page} + + ); +} + +function PaginationArrow({ + href, + direction, + isDisabled, +}: { + href: string; + direction: 'left' | 'right'; + isDisabled?: boolean; +}) { + const className = clsx( + 'flex h-10 w-10 items-center justify-center rounded-md border', + { + 'pointer-events-none text-gray-300': isDisabled, + 'hover:bg-gray-100': !isDisabled, + 'mr-2 md:mr-4': direction === 'left', + 'ml-2 md:ml-4': direction === 'right', + }, + ); + + const icon = + direction === 'left' ? ( + + ) : ( + + ); + + return isDisabled ? ( +
{icon}
+ ) : ( + + {icon} + + ); +} diff --git a/app/ui/invoices/status.tsx b/app/ui/invoices/status.tsx new file mode 100644 index 0000000..db5228d --- /dev/null +++ b/app/ui/invoices/status.tsx @@ -0,0 +1,30 @@ +import { CheckIcon, ClockIcon } from '@heroicons/react/24/outline'; +import clsx from 'clsx'; + +export default function InvoiceStatus({ status }: { status: string }) { + return ( + + {status === 'pending' ? ( + <> + Pending + + + ) : null} + {status === 'paid' ? ( + <> + Paid + + + ) : null} + + ); +} diff --git a/app/ui/invoices/table.tsx b/app/ui/invoices/table.tsx new file mode 100644 index 0000000..0255185 --- /dev/null +++ b/app/ui/invoices/table.tsx @@ -0,0 +1,124 @@ +import Image from 'next/image'; +import { UpdateInvoice, DeleteInvoice } from '@/app/ui/invoices/buttons'; +import InvoiceStatus from '@/app/ui/invoices/status'; +import { formatDateToLocal, formatCurrency } from '@/app/lib/utils'; +import { fetchFilteredInvoices } from '@/app/lib/data'; + +export default async function InvoicesTable({ + query, + currentPage, +}: { + query: string; + currentPage: number; +}) { + const invoices = await fetchFilteredInvoices(query, currentPage); + + return ( +
+
+
+
+ {invoices?.map((invoice) => ( +
+
+
+
+ {`${invoice.name}'s +

{invoice.name}

+
+

{invoice.email}

+
+ +
+
+
+

+ {formatCurrency(invoice.amount)} +

+

{formatDateToLocal(invoice.date)}

+
+
+ + +
+
+
+ ))} +
+ + + + + + + + + + + + + {invoices?.map((invoice) => ( + + + + + + + + + ))} + +
+ Customer + + Email + + Amount + + Date + + Status + + Edit +
+
+ {`${invoice.name}'s +

{invoice.name}

+
+
+ {invoice.email} + + {formatCurrency(invoice.amount)} + + {formatDateToLocal(invoice.date)} + + + +
+ + +
+
+
+
+
+ ); +} diff --git a/app/ui/login-form.tsx b/app/ui/login-form.tsx new file mode 100644 index 0000000..2976b67 --- /dev/null +++ b/app/ui/login-form.tsx @@ -0,0 +1,67 @@ +import { lusitana } from '@/app/ui/fonts'; +import { + AtSymbolIcon, + KeyIcon, + ExclamationCircleIcon, +} from '@heroicons/react/24/outline'; +import { ArrowRightIcon } from '@heroicons/react/20/solid'; +import { Button } from './button'; + +export default function LoginForm() { + return ( +
+
+

+ Please log in to continue. +

+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ {/* Add form errors here */} +
+
+
+ ); +} diff --git a/app/ui/search.tsx b/app/ui/search.tsx new file mode 100644 index 0000000..e6e9391 --- /dev/null +++ b/app/ui/search.tsx @@ -0,0 +1,18 @@ +'use client'; + +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; + +export default function Search({ placeholder }: { placeholder: string }) { + return ( +
+ + + +
+ ); +} diff --git a/app/ui/skeletons.tsx b/app/ui/skeletons.tsx new file mode 100644 index 0000000..74816c4 --- /dev/null +++ b/app/ui/skeletons.tsx @@ -0,0 +1,218 @@ +// Loading animation +const shimmer = + 'before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_2s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/60 before:to-transparent'; + +export function CardSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+ ); +} + +export function CardsSkeleton() { + return ( + <> + + + + + + ); +} + +export function RevenueChartSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+ ); +} + +export function InvoiceSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+ ); +} + +export function LatestInvoicesSkeleton() { + return ( +
+
+
+
+ + + + + +
+
+
+
+
+
+
+ ); +} + +export default function DashboardSkeleton() { + return ( + <> +
+
+ + + + +
+
+ + +
+ + ); +} + +export function TableRowSkeleton() { + return ( + + {/* Customer Name and Image */} + +
+
+
+
+ + {/* Email */} + +
+ + {/* Amount */} + +
+ + {/* Date */} + +
+ + {/* Status */} + +
+ + {/* Actions */} + +
+
+
+
+ + + ); +} + +export function InvoicesMobileSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} + +export function InvoicesTableSkeleton() { + return ( +
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ Customer + + Email + + Amount + + Date + + Status + + Edit +
+
+
+
+ ); +} diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..5e891cf --- /dev/null +++ b/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000..26c03f5 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "private": true, + "scripts": { + "build": "next build", + "dev": "next dev --turbopack", + "start": "next start" + }, + "dependencies": { + "@heroicons/react": "^2.2.0", + "@tailwindcss/forms": "^0.5.10", + "autoprefixer": "10.4.20", + "bcrypt": "^5.1.1", + "clsx": "^2.1.1", + "next": "latest", + "next-auth": "5.0.0-beta.25", + "postcss": "8.5.1", + "postgres": "^3.4.6", + "react": "latest", + "react-dom": "latest", + "tailwindcss": "3.4.17", + "typescript": "5.7.3", + "use-debounce": "^10.0.4", + "zod": "^3.25.17" + }, + "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/node": "22.10.7", + "@types/react": "19.0.7", + "@types/react-dom": "19.0.3" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "bcrypt", + "sharp" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..438d489 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2026 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@heroicons/react': + specifier: ^2.2.0 + version: 2.2.0(react@19.1.0) + '@tailwindcss/forms': + specifier: ^0.5.10 + version: 0.5.10(tailwindcss@3.4.17) + autoprefixer: + specifier: 10.4.20 + version: 10.4.20(postcss@8.5.1) + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + next: + specifier: latest + version: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-auth: + specifier: 5.0.0-beta.25 + version: 5.0.0-beta.25(next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + postcss: + specifier: 8.5.1 + version: 8.5.1 + postgres: + specifier: ^3.4.6 + version: 3.4.6 + react: + specifier: latest + version: 19.1.0 + react-dom: + specifier: latest + version: 19.1.0(react@19.1.0) + tailwindcss: + specifier: 3.4.17 + version: 3.4.17 + typescript: + specifier: 5.7.3 + version: 5.7.3 + use-debounce: + specifier: ^10.0.4 + version: 10.0.4(react@19.1.0) + zod: + specifier: ^3.25.17 + version: 3.25.17 + devDependencies: + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 + '@types/node': + specifier: 22.10.7 + version: 22.10.7 + '@types/react': + specifier: 19.0.7 + version: 19.0.7 + '@types/react-dom': + specifier: 19.0.3 + version: 19.0.3(@types/react@19.0.7) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@auth/core@0.37.2': + resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@heroicons/react@2.2.0': + resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} + peerDependencies: + react: '>= 16 || ^19.0.0-rc' + + '@img/sharp-darwin-arm64@0.34.2': + resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.2': + resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.2': + resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.2': + resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.34.2': + resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.2': + resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.2': + resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.2': + resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.2': + resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.2': + resolution: {integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.2': + resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.2': + resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + + '@next/env@15.3.2': + resolution: {integrity: sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==} + + '@next/swc-darwin-arm64@15.3.2': + resolution: {integrity: sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.3.2': + resolution: {integrity: sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.3.2': + resolution: {integrity: sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@15.3.2': + resolution: {integrity: sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@15.3.2': + resolution: {integrity: sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@15.3.2': + resolution: {integrity: sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@15.3.2': + resolution: {integrity: sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.3.2': + resolution: {integrity: sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/forms@0.5.10': + resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' + + '@types/bcrypt@5.0.2': + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/node@22.10.7': + resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} + + '@types/react-dom@19.0.3': + resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.0.7': + resolution: {integrity: sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==} + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.5: + resolution: {integrity: sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001718: + resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.155: + resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + next-auth@5.0.0-beta.25: + resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + next: ^14.0.0-0 || ^15.0.0-0 + nodemailer: ^6.6.5 + react: ^18.2.0 || ^19.0.0-0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + next@15.3.2: + resolution: {integrity: sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + + oauth4webapi@3.5.1: + resolution: {integrity: sha512-txg/jZQwcbaF7PMJgY7aoxc9QuCxHVFMiEkDIJ60DwDz3PbtXPQnrzo+3X4IRYGChIwWLabRBRpf1k9hO9+xrQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.1: + resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + engines: {node: ^10 || ^12 || >=14} + + postgres@3.4.6: + resolution: {integrity: sha512-MXrZVqgUfiirfZ0MqQqXqZQa6oM5OWFjfuaHVKl7v2zW25LnOJeLpQk+UUW0ZuP94fcS+fZVqyDYm9v5SIJ11Q==} + engines: {node: '>=12'} + + preact-render-to-string@5.2.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + + preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + sharp@0.34.2: + resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} + engines: {node: '>=14.0.0'} + hasBin: true + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-debounce@10.0.4: + resolution: {integrity: sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw==} + engines: {node: '>= 16.0.0'} + peerDependencies: + react: '*' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + + zod@3.25.17: + resolution: {integrity: sha512-8hQzQ/kMOIFbwOgPrm9Sf9rtFHpFUMy4HvN0yEB0spw14aYi0uT5xG5CE2DB9cd51GWNsz+DNO7se1kztHMKnw==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@auth/core@0.37.2': + dependencies: + '@panva/hkdf': 1.2.1 + '@types/cookie': 0.6.0 + cookie: 0.7.1 + jose: 5.10.0 + oauth4webapi: 3.5.1 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@heroicons/react@2.2.0(react@19.1.0)': + dependencies: + react: 19.1.0 + + '@img/sharp-darwin-arm64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.1.0 + optional: true + + '@img/sharp-darwin-x64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.1.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-darwin-x64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-arm@1.1.0': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-s390x@1.1.0': + optional: true + + '@img/sharp-libvips-linux-x64@1.1.0': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + optional: true + + '@img/sharp-linux-arm64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.1.0 + optional: true + + '@img/sharp-linux-arm@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.1.0 + optional: true + + '@img/sharp-linux-s390x@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.1.0 + optional: true + + '@img/sharp-linux-x64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.1.0 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + optional: true + + '@img/sharp-wasm32@0.34.2': + dependencies: + '@emnapi/runtime': 1.4.3 + optional: true + + '@img/sharp-win32-arm64@0.34.2': + optional: true + + '@img/sharp-win32-ia32@0.34.2': + optional: true + + '@img/sharp-win32-x64@0.34.2': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.4 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.7.2 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + + '@next/env@15.3.2': {} + + '@next/swc-darwin-arm64@15.3.2': + optional: true + + '@next/swc-darwin-x64@15.3.2': + optional: true + + '@next/swc-linux-arm64-gnu@15.3.2': + optional: true + + '@next/swc-linux-arm64-musl@15.3.2': + optional: true + + '@next/swc-linux-x64-gnu@15.3.2': + optional: true + + '@next/swc-linux-x64-musl@15.3.2': + optional: true + + '@next/swc-win32-arm64-msvc@15.3.2': + optional: true + + '@next/swc-win32-x64-msvc@15.3.2': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@panva/hkdf@1.2.1': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.4.17 + + '@types/bcrypt@5.0.2': + dependencies: + '@types/node': 22.10.7 + + '@types/cookie@0.6.0': {} + + '@types/node@22.10.7': + dependencies: + undici-types: 6.20.0 + + '@types/react-dom@19.0.3(@types/react@19.0.7)': + dependencies: + '@types/react': 19.0.7 + + '@types/react@19.0.7': + dependencies: + csstype: 3.1.3 + + abbrev@1.1.1: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + aproba@2.0.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + arg@5.0.2: {} + + autoprefixer@10.4.20(postcss@8.5.1): + dependencies: + browserslist: 4.24.5 + caniuse-lite: 1.0.30001718 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.1 + postcss-value-parser: 4.2.0 + + balanced-match@1.0.2: {} + + bcrypt@5.1.1: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.5: + dependencies: + caniuse-lite: 1.0.30001718 + electron-to-chromium: 1.5.155 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.5) + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001718: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@2.0.0: {} + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color-support@1.1.3: {} + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + console-control-strings@1.1.0: {} + + cookie@0.7.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + delegates@1.0.0: {} + + detect-libc@2.0.4: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.155: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + escalade@3.2.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + has-unicode@2.0.1: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-arrayish@0.3.2: + optional: true + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + jose@5.10.0: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lru-cache@10.4.3: {} + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mini-svg-data-uri@1.4.4: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + next-auth@5.0.0-beta.25(next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): + dependencies: + '@auth/core': 0.37.2 + next: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + + next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@next/env': 15.3.2 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001718 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.3.2 + '@next/swc-darwin-x64': 15.3.2 + '@next/swc-linux-arm64-gnu': 15.3.2 + '@next/swc-linux-arm64-musl': 15.3.2 + '@next/swc-linux-x64-gnu': 15.3.2 + '@next/swc-linux-x64-musl': 15.3.2 + '@next/swc-win32-arm64-msvc': 15.3.2 + '@next/swc-win32-x64-msvc': 15.3.2 + sharp: 0.34.2 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-addon-api@5.1.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.19: {} + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + + oauth4webapi@3.5.1: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + package-json-from-dist@1.0.1: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + postcss-import@15.1.0(postcss@8.5.1): + dependencies: + postcss: 8.5.1 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.10 + + postcss-js@4.0.1(postcss@8.5.1): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.1 + + postcss-load-config@4.0.2(postcss@8.5.1): + dependencies: + lilconfig: 3.1.3 + yaml: 2.8.0 + optionalDependencies: + postcss: 8.5.1 + + postcss-nested@6.2.0(postcss@8.5.1): + dependencies: + postcss: 8.5.1 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.1: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres@3.4.6: {} + + preact-render-to-string@5.2.3(preact@10.11.3): + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + + preact@10.11.3: {} + + pretty-format@3.8.0: {} + + queue-microtask@1.2.3: {} + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react@19.1.0: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + semver@7.7.2: {} + + set-blocking@2.0.0: {} + + sharp@0.34.2: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.2 + '@img/sharp-darwin-x64': 0.34.2 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.2 + '@img/sharp-linux-arm64': 0.34.2 + '@img/sharp-linux-s390x': 0.34.2 + '@img/sharp-linux-x64': 0.34.2 + '@img/sharp-linuxmusl-arm64': 0.34.2 + '@img/sharp-linuxmusl-x64': 0.34.2 + '@img/sharp-wasm32': 0.34.2 + '@img/sharp-win32-arm64': 0.34.2 + '@img/sharp-win32-ia32': 0.34.2 + '@img/sharp-win32-x64': 0.34.2 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + + source-map-js@1.2.1: {} + + streamsearch@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + styled-jsx@5.1.6(react@19.1.0): + dependencies: + client-only: 0.0.1 + react: 19.1.0 + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.17: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.1 + postcss-import: 15.1.0(postcss@8.5.1) + postcss-js: 4.0.1(postcss@8.5.1) + postcss-load-config: 4.0.2(postcss@8.5.1) + postcss-nested: 6.2.0(postcss@8.5.1) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + typescript@5.7.3: {} + + undici-types@6.20.0: {} + + update-browserslist-db@1.1.3(browserslist@4.24.5): + dependencies: + browserslist: 4.24.5 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-debounce@10.0.4(react@19.1.0): + dependencies: + react: 19.1.0 + + util-deprecate@1.0.2: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + yallist@4.0.0: {} + + yaml@2.8.0: {} + + zod@3.25.17: {} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/customers/amy-burns.png b/public/customers/amy-burns.png new file mode 100644 index 0000000..7b29d72 Binary files /dev/null and b/public/customers/amy-burns.png differ diff --git a/public/customers/balazs-orban.png b/public/customers/balazs-orban.png new file mode 100644 index 0000000..7fbc009 Binary files /dev/null and b/public/customers/balazs-orban.png differ diff --git a/public/customers/delba-de-oliveira.png b/public/customers/delba-de-oliveira.png new file mode 100644 index 0000000..08db1b8 Binary files /dev/null and b/public/customers/delba-de-oliveira.png differ diff --git a/public/customers/evil-rabbit.png b/public/customers/evil-rabbit.png new file mode 100644 index 0000000..fe7990f Binary files /dev/null and b/public/customers/evil-rabbit.png differ diff --git a/public/customers/lee-robinson.png b/public/customers/lee-robinson.png new file mode 100644 index 0000000..633ae98 Binary files /dev/null and b/public/customers/lee-robinson.png differ diff --git a/public/customers/michael-novotny.png b/public/customers/michael-novotny.png new file mode 100644 index 0000000..96a13a6 Binary files /dev/null and b/public/customers/michael-novotny.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..af98450 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/hero-desktop.png b/public/hero-desktop.png new file mode 100644 index 0000000..de8a5ce Binary files /dev/null and b/public/hero-desktop.png differ diff --git a/public/hero-mobile.png b/public/hero-mobile.png new file mode 100644 index 0000000..bb60e7d Binary files /dev/null and b/public/hero-mobile.png differ diff --git a/public/opengraph-image.png b/public/opengraph-image.png new file mode 100644 index 0000000..569707e Binary files /dev/null and b/public/opengraph-image.png differ diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..bf66330 --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,32 @@ +import type { Config } from 'tailwindcss'; + +const config: Config = { + content: [ + './pages/**/*.{js,ts,jsx,tsx,mdx}', + './components/**/*.{js,ts,jsx,tsx,mdx}', + './app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + gridTemplateColumns: { + '13': 'repeat(13, minmax(0, 1fr))', + }, + colors: { + blue: { + 400: '#2589FE', + 500: '#0070F3', + 600: '#2F6FEB', + }, + }, + }, + keyframes: { + shimmer: { + '100%': { + transform: 'translateX(100%)', + }, + }, + }, + }, + plugins: [require('@tailwindcss/forms')], +}; +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cbd9b64 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "app/lib/placeholder-data.ts", + "scripts/seed.js" + ], + "exclude": ["node_modules"] +}