├── .env.example ├── .github ├── ISSUE_TEMPLATE │ └── sweep-template.yml └── workflows │ └── linter-and-typecheck.yml ├── .gitignore ├── .nvmrc ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Learn.md ├── README.md ├── app ├── dashboard │ ├── (overview) │ │ ├── loading.tsx │ │ └── page.tsx │ ├── customers │ │ ├── [id] │ │ │ └── page.tsx │ │ └── page.tsx │ ├── invoices │ │ ├── [id] │ │ │ ├── edit │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── create │ │ │ └── page.tsx │ │ ├── error.tsx │ │ └── page.tsx │ └── layout.tsx ├── layout.tsx ├── lib │ ├── actions.ts │ ├── data.ts │ ├── definitions.ts │ ├── logger.ts │ ├── placeholder-data.js │ └── utils.ts ├── login │ ├── layout.tsx │ └── page.tsx ├── page.tsx └── ui │ ├── acme-logo.tsx │ ├── button.tsx │ ├── customers │ └── table.tsx │ ├── dashboard │ ├── cards.tsx │ ├── latest-invoices.tsx │ ├── nav-links.tsx │ ├── revenue-chart.tsx │ └── sidenav.tsx │ ├── fonts.ts │ ├── global.css │ ├── home.module.css │ ├── invoices │ ├── breadcrumbs.tsx │ ├── buttons.tsx │ ├── create-form.tsx │ ├── edit-form.tsx │ ├── pagination.tsx │ ├── status.tsx │ └── table.tsx │ ├── login-form.tsx │ ├── search.tsx │ └── skeletons.tsx ├── auth.config.ts ├── auth.ts ├── biome.json ├── cspell-tool.txt ├── cspell.json ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── customers │ ├── amy-burns.png │ ├── balazs-orban.png │ ├── delba-de-oliveira.png │ ├── emil-kowalski.png │ ├── evil-rabbit.png │ ├── guillermo-rauch.png │ ├── hector-simpson.png │ ├── jared-palmer.png │ ├── lee-robinson.png │ ├── michael-novotny.png │ ├── steph-dietz.png │ └── steven-tey.png ├── favicon.ico ├── hero-desktop.png ├── hero-mobile.png └── opengraph-image.png ├── renovate.json ├── scripts └── seed.ts ├── sweep.yaml ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Copy from .env.local on the Vercel dashboard 2 | # https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database 3 | POSTGRES_URL= 4 | POSTGRES_PRISMA_URL= 5 | POSTGRES_URL_NON_POOLING= 6 | POSTGRES_USER= 7 | POSTGRES_HOST= 8 | POSTGRES_PASSWORD= 9 | POSTGRES_DATABASE= 10 | 11 | # `openssl rand -base64 32` 12 | AUTH_SECRET= 13 | AUTH_URL=http://localhost:3000/api/auth -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/sweep-template.yml: -------------------------------------------------------------------------------- 1 | name: Sweep Issue 2 | title: 'Sweep: ' 3 | description: For small bugs, features, refactors, and tests to be handled by Sweep, an AI-powered junior developer. 4 | labels: sweep 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Details 10 | description: Tell Sweep where and what to edit and provide enough context for a new developer to the codebase 11 | placeholder: | 12 | Unit Tests: Write unit tests for . Test each function in the file. Make sure to test edge cases. 13 | Bugs: The bug might be in . Here are the logs: ... 14 | Features: the new endpoint should use the ... class from because it contains ... logic. 15 | Refactors: We are migrating this function to ... version because ... -------------------------------------------------------------------------------- /.github/workflows/linter-and-typecheck.yml: -------------------------------------------------------------------------------- 1 | name: Linter & Typecheck 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | jobs: 11 | typecheck: 12 | runs-on: ubuntu-latest 13 | continue-on-error: true 14 | steps: 15 | - name: 🛑 Cancel Previous Runs 16 | uses: styfle/cancel-workflow-action@0.12.1 17 | - id: checkout 18 | name: Checkout 19 | uses: actions/checkout@v4 20 | - name: Install bun 21 | uses: oven-sh/setup-bun@v2 22 | - name: Install dependencies 23 | run: bun install 24 | - id: Typecheck 25 | name: Checking Typescript Error 26 | run: | 27 | bun run typecheck 28 | linter: 29 | runs-on: ubuntu-latest 30 | continue-on-error: true 31 | steps: 32 | - name: 🛑 Cancel Previous Runs 33 | uses: styfle/cancel-workflow-action@0.12.1 34 | - id: checkout 35 | name: Checkout 36 | uses: actions/checkout@v4 37 | - name: Install bun 38 | uses: oven-sh/setup-bun@v2 39 | - name: Install dependencies 40 | run: bun install 41 | - id: linter 42 | name: Linter check 43 | run: | 44 | bun run lint 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/mirrors-prettier 3 | rev: "v4.0.0-alpha.8" 4 | hooks: 5 | - id: prettier 6 | # Those are not supported by biomejs yet, refer https://biomejs.dev/internals/language-support/ 7 | types_or: [html, css, markdown] 8 | description: "Prettify files for the files which Biomejs doesn't support yet" 9 | - repo: https://github.com/biomejs/pre-commit 10 | rev: "v0.1.0" 11 | hooks: 12 | - id: biome-check 13 | additional_dependencies: ["@biomejs/biome@1.7.3"] 14 | description: "Format, organize imports, lint, and apply safe fixes to the committed files" 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Learn Next.js 14 - App Router Course 2 | 3 | This document provides guidelines for contributing to this project. Please ensure you follow these guidelines to maintain the quality and consistency of the codebase. 4 | 5 | ## Commit Messages 6 | 7 | Commit messages are an essential part of the development process. They provide a brief description of the changes made in the commit. Here are some best practices for writing commit messages: 8 | 9 | - Do not include personal notes or comments in the commit messages. 10 | - Commit messages should be concise and describe the changes made in the commit. 11 | - Use the imperative mood ("Add feature" not "Added feature" or "Adds feature"). 12 | - Start the message with a capital letter. 13 | - Do not end the message with a period. 14 | 15 | For more information about the project and how to get it up and running, please refer to the [README.md](./README.md) file. 16 | -------------------------------------------------------------------------------- /Learn.md: -------------------------------------------------------------------------------- 1 | # Learn Nextjs 14 2 | 3 | - This is an early iteration. Content and code may change. 4 | 5 | ## Chapter 1 - Setup new project 6 | 7 | - Setup Nextjs 14 with dashboard template. 8 | - Key points from this chapter: 9 | - Explanation of the overall project structure. 10 | - Usage of Typescript. 11 | 12 | ## Chapter 2 - CSS styling 13 | 14 | - Explain how to use Tailwind CSS or CSS Modules. 15 | - Good to introduce `clsx` to combine multiple class names 16 | - The import with '@/' convention for CSS files could be improved for better navigation. 17 | - The '@/' convention for CSS file imports does not allow for file navigation on click. 18 | 19 | P/S: The absolute path for TS/JS file is working fine. 20 | 21 | ## Chapter 3 - Optimizing Fonts and Images 22 | 23 | - Justify why we need to optimize fonts and images. 24 | - Explain how to use Google Font with nextjs. 25 | - Explain how to use Image component with Tailwind CSS for swapping image between different screen sizes. 26 | 27 | ## Chapter 4 - Layouts and Pages 28 | 29 | - Good visual explanation for app layout and pages. 30 | - Introduce root layout vs page layout concept. 31 | - Use root layout for modify html and body tag. 32 | - Page layout is for each page/route. 33 | 34 | ## Chapter 5 - Navigating between pages 35 | 36 | - Explain how to use Link component and how to code splitting works. 37 | - Nice introduction for `use client` for detecting active link. 38 | 39 | ## Chapter 6 - DB 40 | 41 | - This chapter is about how to use @vercel/postgres to connect to Postgres DB and populate the database. 42 | - The seed.js has been migrated to typescript and a logger has been added to the lib folder. 43 | - This chapter can be skipped if already familiar with using Postgres. Alternatives like Prisma/Drizzle can be considered. 44 | 45 | ## Chapter 7 - Fetching data 46 | 47 | - Not focus on data fetching, need to refer to [nextjs documentation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating) 48 | - It seems the main focus is about `Server Components` which is [React beta feature](https://react.dev/reference/react/use-server). 49 | 50 | ## Chapter 8 - Static and Dynamic Rendering 51 | 52 | - This chapter uses a unstable feature to support dynamic rendering. 53 | 54 | ```typescript 55 | import { unstable_noStore as noStore } from "next/cache"; 56 | 57 | // Add noStore() here prevent the response from being cached. 58 | // This is equivalent to in fetch(..., {cache: 'no-store'}). 59 | noStore(); 60 | ``` 61 | 62 | - When use Promise.all, the slowest promise will determine the speed of the response. 63 | 64 | ## Chapter 9 - Streaming 65 | 66 | - Nice to see how to use skeleton loading with Streaming in nextjs. 67 | - Good to introduce new concept for route: Route Groups 68 | 69 | ## Chapter 10 - Partial Prerendering 70 | 71 | - This is a new experimental feature in nextjs. 72 | - They have a nice explanation for this feature. Refer to blog post [here](https://vercel.com/blog/partial-prerendering-with-next-js-creating-a-new-default-rendering-model). 73 | 74 | Remix has had a similar feature for some time. For comparison, refer to: [Next.js 13 vs Remix: An In-depth case study](https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/) 75 | 76 | ## Chapter 11 - Search and Pagination 77 | 78 | - This chapter is about how to implement search and pagination. 79 | - Good to introduce `use-debounce` library for handle debouncing for search input 80 | 81 | ## Chapter 12 - Mutating Data 82 | 83 | - Server action is experimental feature in [React](https://react.dev/reference/react/use-server) 84 | - This chapter is about how to use server action to mutate data. 85 | 86 | ## Chapter 13 - Handling Errors 87 | 88 | - How to handle error with try/catch and show on UI with error.tsx file. 89 | - How to use `notFound()` to handle 404 error. 90 | 91 | ## Chapter 14 - Validation & Accessibility 92 | 93 | - How to use experimental hook: [useFormState](https://react.dev/reference/react-dom/hooks/useFormState#useformstate) for validation. 94 | - Nice to introduce accessibility concept with `aria-describedby`. 95 | 96 | ## Chapter 15 - Authentication 97 | 98 | - How to use next-auth for authentication. 99 | - This also bring new hook for form: [useFormStatus](https://react.dev/reference/react-dom/hooks/useFormStatus) 100 | 101 | ## Chapter 16 - Metadata 102 | 103 | - This technique is useful for SEO. 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Learn Next.js 14 - App Router Course 👋

2 |

3 | 4 | Next.js version 5 | 6 | 7 | Node.js version 8 | 9 |

10 | 11 | [![IT Man - First Impressions: Diving into the Free Next.js 14 Course [Vietnamese]](https://i.ytimg.com/vi/ccV582wobvQ/hqdefault.jpg)](https://www.youtube.com/watch?v=ccV582wobvQ) 12 | 13 | ## About The Course 14 | 15 | Welcome to the "Next.js App Router Course - Starter." This repository serves as the starting point for your journey into building a sophisticated dashboard application with the latest features of Next.js 14. 16 | 17 | Explore the [course curriculum](https://nextjs.org/learn) for an in-depth understanding of what's ahead. 18 | 19 | ## Prerequisites 20 | 21 | - node >=18 22 | 23 | ## Install 24 | 25 | ```sh 26 | npm install 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```sh 32 | npm run dev 33 | ``` 34 | 35 | ## Learning Experience 36 | 37 | Dive into my learning journey and the key takeaways from this course: 38 | 39 | - [My Learning Notes](./Learn.md) 40 | 41 | This document captures insights on setting up Next.js with TypeScript, styling with Tailwind CSS, optimizing assets, and more, along with my personal observations and preferences. 42 | 43 | ## References 44 | 45 | - [Next.js 13 vs Remix: An In-depth case study](https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/) 46 | - [Building towards a new default rendering model for web applications – Vercel](https://vercel.com/blog/partial-prerendering-with-next-js-creating-a-new-default-rendering-model) 47 | - [Server Action – React](https://react.dev/reference/react/use-server) 48 | - [Metadata with Next.js](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) 49 | 50 | ## Show your support 51 | 52 | Give a ⭐️ if this project helped you! 53 | -------------------------------------------------------------------------------- /app/dashboard/(overview)/loading.tsx: -------------------------------------------------------------------------------- 1 | import DashboardSkeleton from '@/app/ui/skeletons' 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /app/dashboard/(overview)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | 3 | import CardWrapper from '@/app/ui/dashboard/cards' 4 | import LatestInvoices from '@/app/ui/dashboard/latest-invoices' 5 | import RevenueChart from '@/app/ui/dashboard/revenue-chart' 6 | import { lusitana } from '@/app/ui/fonts' 7 | import { 8 | CardsSkeleton, 9 | LatestInvoicesSkeleton, 10 | RevenueChartSkeleton, 11 | } from '@/app/ui/skeletons' 12 | 13 | export default async function Page() { 14 | return ( 15 |
16 |

17 | Dashboard 18 |

19 |
20 | }> 21 | 22 | 23 |
24 |
25 | }> 26 | 27 | 28 | }> 29 | 30 | 31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /app/dashboard/customers/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page(props: unknown) { 2 | return ( 3 |
4 | Customer Page 5 |
{JSON.stringify(props, null, 2)}
6 |
7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /app/dashboard/customers/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Suspense } from 'react' 3 | 4 | import { fetchFilteredCustomers } from '@/app/lib/data' 5 | import CustomersTable from '@/app/ui/customers/table' 6 | 7 | export const metadata: Metadata = { 8 | title: 'Customers | Acme Dashboard', 9 | } 10 | 11 | export default async function Page({ 12 | searchParams, 13 | }: { 14 | searchParams?: { 15 | query?: string 16 | } 17 | }) { 18 | const query = searchParams?.query ?? '' 19 | const customers = await fetchFilteredCustomers(query) 20 | 21 | return ( 22 |
23 | Loading

}> 24 | 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/dashboard/invoices/[id]/edit/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { FaceFrownIcon } from '@heroicons/react/24/outline' 2 | 3 | import Link from 'next/link' 4 | 5 | export default function NotFound() { 6 | return ( 7 |
8 | 9 |

404 Not Found

10 |

Could not find the requested invoice.

11 | 15 | Go Back 16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /app/dashboard/invoices/[id]/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | import { notFound } from 'next/navigation' 4 | 5 | import { fetchCustomers, fetchInvoiceById } from '@/app/lib/data' 6 | import Breadcrumbs from '@/app/ui/invoices/breadcrumbs' 7 | import EditInvoiceForm from '@/app/ui/invoices/edit-form' 8 | 9 | export const metadata: Metadata = { 10 | title: 'Edit Invoice | Acme Dashboard', 11 | } 12 | 13 | export default async function Page({ params }: { params: { id: string } }) { 14 | const { id } = params 15 | const [invoice, customers] = await Promise.all([ 16 | fetchInvoiceById(id), 17 | fetchCustomers(), 18 | ]) 19 | 20 | if (!invoice) { 21 | notFound() 22 | } 23 | 24 | return ( 25 |
26 | 36 | 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /app/dashboard/invoices/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return
Invoice Page
3 | } 4 | -------------------------------------------------------------------------------- /app/dashboard/invoices/create/page.tsx: -------------------------------------------------------------------------------- 1 | import { fetchCustomers } from '@/app/lib/data' 2 | import Breadcrumbs from '@/app/ui/invoices/breadcrumbs' 3 | import CreateInvoiceForm from '@/app/ui/invoices/create-form' 4 | 5 | export default async function CreateInvoicePage() { 6 | const customers = await fetchCustomers() 7 | 8 | return ( 9 |
10 | 20 | 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /app/dashboard/invoices/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect } from 'react' 4 | 5 | import logger from '@/app/lib/logger' 6 | 7 | export default function ErrorPage({ 8 | error, 9 | reset, 10 | }: { 11 | readonly error: Error & { digest?: string } 12 | readonly reset: () => void 13 | }) { 14 | useEffect(() => { 15 | // Optionally log the error to an error reporting service 16 | logger.error(error) 17 | }, [error]) 18 | 19 | return ( 20 |
21 |

Something went wrong!

22 | 34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /app/dashboard/invoices/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Suspense } from 'react' 3 | 4 | import { fetchInvoicesPages } from '@/app/lib/data' 5 | import { lusitana } from '@/app/ui/fonts' 6 | import { CreateInvoice } from '@/app/ui/invoices/buttons' 7 | import Pagination from '@/app/ui/invoices/pagination' 8 | import InvoicesTable from '@/app/ui/invoices/table' 9 | import Search from '@/app/ui/search' 10 | import { InvoicesTableSkeleton } from '@/app/ui/skeletons' 11 | 12 | export const metadata: Metadata = { 13 | title: 'Invoices | Acme Dashboard', 14 | } 15 | 16 | export default async function Page({ 17 | searchParams, 18 | }: { 19 | searchParams?: { 20 | query?: string 21 | page?: string 22 | } 23 | }) { 24 | const query = searchParams?.query ?? '' 25 | const currentPage = Number(searchParams?.page) || 1 26 | 27 | const totalPages = await fetchInvoicesPages(query) 28 | 29 | return ( 30 |
31 |
32 |

Invoices

33 |
34 |
35 | 36 | 37 |
38 | }> 39 | 40 | 41 |
42 | 43 |
44 |
45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /app/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react' 2 | 3 | import SideNav from '@/app/ui/dashboard/sidenav' 4 | 5 | export default function Layout({ children }: { readonly children: ReactNode }) { 6 | return ( 7 |
8 |
9 | 10 |
11 |
{children}
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import type { ReactNode } from 'react' 3 | 4 | import { inter } from './ui/fonts' 5 | import './ui/global.css' 6 | 7 | export const metadata: Metadata = { 8 | title: { 9 | template: '%s | Acme Dashboard', 10 | default: 'Acme Dashboard', 11 | }, 12 | description: 'The official Next.js Course Dashboard, built with App Router.', 13 | metadataBase: new URL('https://nextjs14.productsway.com'), 14 | } 15 | 16 | export default function RootLayout({ 17 | children, 18 | }: { 19 | readonly children: ReactNode 20 | }) { 21 | return ( 22 | 23 | {children} 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /app/lib/actions.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import { sql } from '@vercel/postgres' 4 | import { z } from 'zod' 5 | 6 | import { revalidatePath } from 'next/cache' 7 | import { redirect } from 'next/navigation' 8 | 9 | import { signIn } from '@/auth' 10 | 11 | import logger from './logger' 12 | 13 | const InvoiceSchema = z.object({ 14 | id: z.string(), 15 | customerId: z.string({ 16 | invalid_type_error: 'Please select a customer.', 17 | }), 18 | amount: z.coerce 19 | .number() 20 | .gt(0, { message: 'Please enter an amount greater than $0.' }), 21 | status: z.enum(['pending', 'paid'], { 22 | invalid_type_error: 'Please select an invoice status.', 23 | }), 24 | date: z.string(), 25 | }) 26 | 27 | const CreateInvoice = InvoiceSchema.omit({ id: true, date: true }) 28 | const UpdateInvoice = InvoiceSchema.omit({ date: true, id: true }) 29 | 30 | // This is temporary until @types/react-dom is updated 31 | export type State = { 32 | errors?: { 33 | customerId?: string[] 34 | amount?: string[] 35 | status?: string[] 36 | } 37 | message?: string | undefined 38 | } 39 | 40 | export async function createInvoice(_prevState: State, formData: FormData) { 41 | const rawFormData = Object.fromEntries(formData.entries()) 42 | 43 | logger.info('createInvoice - server action called') 44 | const invoiceFormData = CreateInvoice.safeParse(rawFormData) 45 | logger.info(invoiceFormData) 46 | 47 | // If form validation fails, return errors early. Otherwise, continue. 48 | if (!invoiceFormData.success) { 49 | return { 50 | errors: invoiceFormData.error.flatten().fieldErrors, 51 | message: 'Missing Fields. Failed to Create Invoice.', 52 | } 53 | } 54 | 55 | const { customerId, amount, status } = invoiceFormData.data 56 | const amountInCents = amount * 100 57 | const date = new Date().toISOString().split('T')[0] 58 | 59 | try { 60 | await sql` 61 | INSERT INTO invoices (customer_id, amount, status, date) 62 | VALUES (${customerId}, ${amountInCents}, ${status}, ${date}) 63 | ` 64 | } catch (e) { 65 | logger.error(e) 66 | return { message: 'Database Error: Failed to Delete Invoice' } 67 | } 68 | 69 | revalidatePath('/dashboard/invoices') 70 | redirect('/dashboard/invoices') 71 | } 72 | 73 | export async function updateInvoice(id: string, formData: FormData) { 74 | const rawFormData = Object.fromEntries(formData.entries()) 75 | 76 | logger.info('updateInvoice - server action called') 77 | const data = UpdateInvoice.parse(rawFormData) 78 | logger.info(data) 79 | 80 | const amountInCents = data.amount * 100 81 | try { 82 | await sql` 83 | UPDATE invoices 84 | SET customer_id = ${data.customerId}, amount = ${amountInCents}, status = ${data.status} 85 | WHERE id = ${id} 86 | ` 87 | } catch (e) { 88 | logger.error(e) 89 | return { message: 'Database Error: Failed to Update Invoice' } 90 | } 91 | 92 | revalidatePath('/dashboard/invoices') 93 | redirect('/dashboard/invoices') 94 | } 95 | 96 | export async function deleteInvoice(id: string) { 97 | logger.info('deleteInvoice - server action called') 98 | 99 | try { 100 | await sql`DELETE FROM invoices WHERE id = ${id}` 101 | } catch (e) { 102 | logger.error(e) 103 | return { message: 'Database Error: Failed to Delete Invoice' } 104 | } 105 | 106 | revalidatePath('/dashboard/invoices') 107 | } 108 | 109 | export async function authenticate( 110 | _prevState: string | undefined, 111 | formData: FormData, 112 | ) { 113 | try { 114 | logger.info('authenticate - server action called') 115 | await signIn('credentials', Object.fromEntries(formData)) 116 | } catch (error) { 117 | if ((error as Error).message.includes('CredentialsSignin')) { 118 | return 'CredentialSignin' 119 | } 120 | 121 | throw error 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/lib/data.ts: -------------------------------------------------------------------------------- 1 | import { sql } from '@vercel/postgres' 2 | 3 | import { unstable_noStore as noStore } from 'next/cache' 4 | 5 | import type { 6 | CustomerField, 7 | CustomerTable, 8 | InvoiceForm, 9 | InvoiceTable, 10 | LatestInvoiceRaw, 11 | Revenue, 12 | User, 13 | } from './definitions' 14 | import logger from './logger' 15 | import { formatCurrency } from './utils' 16 | 17 | export async function fetchRevenue() { 18 | // Add noStore() here prevent the response from being cached. 19 | // This is equivalent to in fetch(..., {cache: 'no-store'}). 20 | noStore() 21 | 22 | try { 23 | // Artificially delay a response for demo purposes. 24 | // Don't do this in real life :) 25 | 26 | // logger.info("Fetching revenue data..."); 27 | // await new Promise((resolve) => setTimeout(resolve, 3000)); 28 | 29 | const data = await sql`SELECT * FROM revenue` 30 | 31 | // Logger.info("Data fetch complete after 3 seconds."); 32 | 33 | return data.rows 34 | } catch (error) { 35 | logger.error('Database Error:', error) 36 | throw new Error('Failed to fetch revenue data.') 37 | } 38 | } 39 | 40 | export async function fetchLatestInvoices() { 41 | noStore() 42 | 43 | try { 44 | const data = await sql` 45 | SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id 46 | FROM invoices 47 | JOIN customers ON invoices.customer_id = customers.id 48 | ORDER BY invoices.date DESC 49 | LIMIT 5` 50 | 51 | const latestInvoices = data.rows.map((invoice) => ({ 52 | ...invoice, 53 | amount: formatCurrency(invoice.amount), 54 | })) 55 | return latestInvoices 56 | } catch (error) { 57 | logger.error('Database Error:', error) 58 | throw new Error('Failed to fetch the latest invoices.') 59 | } 60 | } 61 | 62 | export async function fetchCardData() { 63 | noStore() 64 | 65 | try { 66 | // You can probably combine these into a single SQL query 67 | // However, we are intentionally splitting them to demonstrate 68 | // how to initialize multiple queries in parallel with JS. 69 | const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices` 70 | const customerCountPromise = sql`SELECT COUNT(*) FROM customers` 71 | const invoiceStatusPromise = sql`SELECT 72 | SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid", 73 | SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending" 74 | FROM invoices` 75 | 76 | const data = await Promise.all([ 77 | invoiceCountPromise, 78 | customerCountPromise, 79 | invoiceStatusPromise, 80 | ]) 81 | 82 | const numberOfInvoices = Number(data[0].rows[0].count ?? '0') 83 | const numberOfCustomers = Number(data[1].rows[0].count ?? '0') 84 | const totalPaidInvoices = formatCurrency( 85 | Number(data[2].rows[0].paid ?? '0'), 86 | ) 87 | const totalPendingInvoices = formatCurrency( 88 | Number(data[2].rows[0].pending ?? '0'), 89 | ) 90 | 91 | return { 92 | numberOfCustomers, 93 | numberOfInvoices, 94 | totalPaidInvoices, 95 | totalPendingInvoices, 96 | } 97 | } catch (error) { 98 | logger.error('Database Error:', error) 99 | throw new Error('Failed to card data.') 100 | } 101 | } 102 | 103 | const ITEMS_PER_PAGE = 6 104 | export async function fetchFilteredInvoices( 105 | query: string, 106 | currentPage: number, 107 | ) { 108 | noStore() 109 | 110 | const offset = (currentPage - 1) * ITEMS_PER_PAGE 111 | 112 | try { 113 | const invoices = await sql` 114 | SELECT 115 | invoices.id, 116 | invoices.amount, 117 | invoices.date, 118 | invoices.status, 119 | customers.name, 120 | customers.email, 121 | customers.image_url 122 | FROM invoices 123 | JOIN customers ON invoices.customer_id = customers.id 124 | WHERE 125 | customers.name ILIKE ${`%${query}%`} OR 126 | customers.email ILIKE ${`%${query}%`} OR 127 | invoices.amount::text ILIKE ${`%${query}%`} OR 128 | invoices.date::text ILIKE ${`%${query}%`} OR 129 | invoices.status ILIKE ${`%${query}%`} 130 | ORDER BY invoices.date DESC 131 | LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset} 132 | ` 133 | 134 | return invoices.rows 135 | } catch (error) { 136 | logger.error('Database Error:', error) 137 | throw new Error('Failed to fetch invoices.') 138 | } 139 | } 140 | 141 | export async function fetchInvoicesPages(query: string) { 142 | noStore() 143 | 144 | try { 145 | const count = await sql`SELECT COUNT(*) 146 | FROM invoices 147 | JOIN customers ON invoices.customer_id = customers.id 148 | WHERE 149 | customers.name ILIKE ${`%${query}%`} OR 150 | customers.email ILIKE ${`%${query}%`} OR 151 | invoices.amount::text ILIKE ${`%${query}%`} OR 152 | invoices.date::text ILIKE ${`%${query}%`} OR 153 | invoices.status ILIKE ${`%${query}%`} 154 | ` 155 | 156 | const totalPages = Math.ceil(Number(count.rows[0].count) / ITEMS_PER_PAGE) 157 | return totalPages 158 | } catch (error) { 159 | logger.error('Database Error:', error) 160 | throw new Error('Failed to fetch total number of invoices.') 161 | } 162 | } 163 | 164 | export async function fetchInvoiceById(id: string) { 165 | noStore() 166 | 167 | try { 168 | const data = await sql` 169 | SELECT 170 | invoices.id, 171 | invoices.customer_id, 172 | invoices.amount, 173 | invoices.status 174 | FROM invoices 175 | WHERE invoices.id = ${id}; 176 | ` 177 | 178 | const invoice = data.rows.map((invoice) => ({ 179 | ...invoice, 180 | // Convert amount from cents to dollars 181 | amount: invoice.amount / 100, 182 | })) 183 | 184 | return invoice[0] 185 | } catch (error) { 186 | logger.error('Database Error:', error) 187 | } 188 | } 189 | 190 | export async function fetchCustomers() { 191 | noStore() 192 | 193 | try { 194 | const data = await sql` 195 | SELECT 196 | id, 197 | name 198 | FROM customers 199 | ORDER BY name ASC 200 | ` 201 | 202 | const customers = data.rows 203 | return customers 204 | } catch (err) { 205 | logger.error('Database Error:', err) 206 | throw new Error('Failed to fetch all customers.') 207 | } 208 | } 209 | 210 | export async function fetchFilteredCustomers(query: string) { 211 | noStore() 212 | 213 | try { 214 | const data = await sql` 215 | SELECT 216 | customers.id, 217 | customers.name, 218 | customers.email, 219 | customers.image_url, 220 | COUNT(invoices.id) AS total_invoices, 221 | SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending, 222 | SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid 223 | FROM customers 224 | LEFT JOIN invoices ON customers.id = invoices.customer_id 225 | WHERE 226 | customers.name ILIKE ${`%${query}%`} OR 227 | customers.email ILIKE ${`%${query}%`} 228 | GROUP BY customers.id, customers.name, customers.email, customers.image_url 229 | ORDER BY customers.name ASC 230 | ` 231 | 232 | const customers = data.rows.map((customer) => ({ 233 | ...customer, 234 | total_pending: formatCurrency(customer.total_pending), 235 | total_paid: formatCurrency(customer.total_paid), 236 | })) 237 | 238 | return customers 239 | } catch (err) { 240 | logger.error('Database Error:', err) 241 | throw new Error('Failed to fetch customer table.') 242 | } 243 | } 244 | 245 | export async function getUser(email: string) { 246 | try { 247 | const user = await sql`SELECT * from USERS where email=${email}` 248 | return user.rows[0] as User 249 | } catch (error) { 250 | logger.error('Failed to fetch user:', error) 251 | throw new Error('Failed to fetch user.') 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /app/lib/definitions.ts: -------------------------------------------------------------------------------- 1 | // This file contains type definitions for your data. 2 | // It describes the shape of the data, and what data type each property should accept. 3 | // For simplicity of teaching, we're manually defining these types. 4 | // However, these types are generated automatically if you're using an ORM such as Prisma. 5 | export type User = { 6 | id: string 7 | name: string 8 | email: string 9 | password: string 10 | } 11 | 12 | export type Customer = { 13 | id: string 14 | name: string 15 | email: string 16 | image_url: string 17 | } 18 | 19 | export type Invoice = { 20 | id: string 21 | customer_id: string 22 | amount: number 23 | date: string 24 | // In TypeScript, this is called a string union type. 25 | // It means that the "status" property can only be one of the two strings: 'pending' or 'paid'. 26 | status: 'pending' | 'paid' 27 | } 28 | 29 | export type Revenue = { 30 | month: string 31 | revenue: number 32 | } 33 | 34 | export type LatestInvoice = { 35 | id: string 36 | name: string 37 | image_url: string 38 | email: string 39 | amount: string 40 | } 41 | 42 | // The database returns a number for amount, but we later format it to a string with the formatCurrency function 43 | export type LatestInvoiceRaw = Omit & { 44 | amount: number 45 | } 46 | 47 | export type InvoiceTable = { 48 | id: string 49 | customer_id: string 50 | name: string 51 | email: string 52 | image_url: string 53 | date: string 54 | amount: number 55 | status: 'pending' | 'paid' 56 | } 57 | 58 | export type CustomerTable = { 59 | id: string 60 | name: string 61 | email: string 62 | image_url: string 63 | total_invoices: number 64 | total_pending: number 65 | total_paid: number 66 | } 67 | 68 | export type FormattedCustomersTable = { 69 | id: string 70 | name: string 71 | email: string 72 | image_url: string 73 | total_invoices: number 74 | total_pending: string 75 | total_paid: string 76 | } 77 | 78 | export type CustomerField = { 79 | id: string 80 | name: string 81 | } 82 | 83 | export type InvoiceForm = { 84 | id: string 85 | customer_id: string 86 | amount: number 87 | status: 'pending' | 'paid' 88 | } 89 | -------------------------------------------------------------------------------- /app/lib/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from 'pino' 2 | 3 | const logger = pino({}) 4 | 5 | export default logger 6 | -------------------------------------------------------------------------------- /app/lib/placeholder-data.js: -------------------------------------------------------------------------------- 1 | // This file contains placeholder data that you'll be replacing with real data in the Data Fetching chapter: 2 | // https://nextjs.org/learn/dashboard-app/fetching-data 3 | const users = [ 4 | { 5 | id: '410544b2-4001-4271-9855-fec4b6a6442a', 6 | name: 'User', 7 | email: 'user@nextmail.com', 8 | password: '123456', 9 | }, 10 | ] 11 | 12 | const customers = [ 13 | { 14 | id: '3958dc9e-712f-4377-85e9-fec4b6a6442a', 15 | name: 'Delba de Oliveira', 16 | email: 'delba@oliveira.com', 17 | image_url: '/customers/delba-de-oliveira.png', 18 | }, 19 | { 20 | id: '3958dc9e-742f-4377-85e9-fec4b6a6442a', 21 | name: 'Lee Robinson', 22 | email: 'lee@robinson.com', 23 | image_url: '/customers/lee-robinson.png', 24 | }, 25 | { 26 | id: '3958dc9e-737f-4377-85e9-fec4b6a6442a', 27 | name: 'Hector Simpson', 28 | email: 'hector@simpson.com', 29 | image_url: '/customers/hector-simpson.png', 30 | }, 31 | { 32 | id: '50ca3e18-62cd-11ee-8c99-0242ac120002', 33 | name: 'Steven Tey', 34 | email: 'steven@tey.com', 35 | image_url: '/customers/steven-tey.png', 36 | }, 37 | { 38 | id: '3958dc9e-787f-4377-85e9-fec4b6a6442a', 39 | name: 'Steph Dietz', 40 | email: 'steph@dietz.com', 41 | image_url: '/customers/steph-dietz.png', 42 | }, 43 | { 44 | id: '76d65c26-f784-44a2-ac19-586678f7c2f2', 45 | name: 'Michael Novotny', 46 | email: 'michael@novotny.com', 47 | image_url: '/customers/michael-novotny.png', 48 | }, 49 | { 50 | id: 'd6e15727-9fe1-4961-8c5b-ea44a9bd81aa', 51 | name: 'Evil Rabbit', 52 | email: 'evil@rabbit.com', 53 | image_url: '/customers/evil-rabbit.png', 54 | }, 55 | { 56 | id: '126eed9c-c90c-4ef6-a4a8-fcf7408d3c66', 57 | name: 'Emil Kowalski', 58 | email: 'emil@kowalski.com', 59 | image_url: '/customers/emil-kowalski.png', 60 | }, 61 | { 62 | id: 'CC27C14A-0ACF-4F4A-A6C9-D45682C144B9', 63 | name: 'Amy Burns', 64 | email: 'amy@burns.com', 65 | image_url: '/customers/amy-burns.png', 66 | }, 67 | { 68 | id: '13D07535-C59E-4157-A011-F8D2EF4E0CBB', 69 | name: 'Balazs Orban', 70 | email: 'balazs@orban.com', 71 | image_url: '/customers/balazs-orban.png', 72 | }, 73 | ] 74 | 75 | const invoices = [ 76 | { 77 | customer_id: customers[0].id, 78 | amount: 15795, 79 | status: 'pending', 80 | date: '2022-12-06', 81 | }, 82 | { 83 | customer_id: customers[1].id, 84 | amount: 20348, 85 | status: 'pending', 86 | date: '2022-11-14', 87 | }, 88 | { 89 | customer_id: customers[4].id, 90 | amount: 3040, 91 | status: 'paid', 92 | date: '2022-10-29', 93 | }, 94 | { 95 | customer_id: customers[3].id, 96 | amount: 44800, 97 | status: 'paid', 98 | date: '2023-09-10', 99 | }, 100 | { 101 | customer_id: customers[5].id, 102 | amount: 34577, 103 | status: 'pending', 104 | date: '2023-08-05', 105 | }, 106 | { 107 | customer_id: customers[7].id, 108 | amount: 54246, 109 | status: 'pending', 110 | date: '2023-07-16', 111 | }, 112 | { 113 | customer_id: customers[6].id, 114 | amount: 666, 115 | status: 'pending', 116 | date: '2023-06-27', 117 | }, 118 | { 119 | customer_id: customers[3].id, 120 | amount: 32545, 121 | status: 'paid', 122 | date: '2023-06-09', 123 | }, 124 | { 125 | customer_id: customers[4].id, 126 | amount: 1250, 127 | status: 'paid', 128 | date: '2023-06-17', 129 | }, 130 | { 131 | customer_id: customers[5].id, 132 | amount: 8546, 133 | status: 'paid', 134 | date: '2023-06-07', 135 | }, 136 | { 137 | customer_id: customers[1].id, 138 | amount: 500, 139 | status: 'paid', 140 | date: '2023-08-19', 141 | }, 142 | { 143 | customer_id: customers[5].id, 144 | amount: 8945, 145 | status: 'paid', 146 | date: '2023-06-03', 147 | }, 148 | { 149 | customer_id: customers[2].id, 150 | amount: 8945, 151 | status: 'paid', 152 | date: '2023-06-18', 153 | }, 154 | { 155 | customer_id: customers[0].id, 156 | amount: 8945, 157 | status: 'paid', 158 | date: '2023-10-04', 159 | }, 160 | { 161 | customer_id: customers[2].id, 162 | amount: 1000, 163 | status: 'paid', 164 | date: '2022-06-05', 165 | }, 166 | ] 167 | 168 | const revenue = [ 169 | { month: 'Jan', revenue: 2000 }, 170 | { month: 'Feb', revenue: 1800 }, 171 | { month: 'Mar', revenue: 2200 }, 172 | { month: 'Apr', revenue: 2500 }, 173 | { month: 'May', revenue: 2300 }, 174 | { month: 'Jun', revenue: 3200 }, 175 | { month: 'Jul', revenue: 3500 }, 176 | { month: 'Aug', revenue: 3700 }, 177 | { month: 'Sep', revenue: 2500 }, 178 | { month: 'Oct', revenue: 2800 }, 179 | { month: 'Nov', revenue: 3000 }, 180 | { month: 'Dec', revenue: 4800 }, 181 | ] 182 | 183 | module.exports = { 184 | users, 185 | customers, 186 | invoices, 187 | revenue, 188 | } 189 | -------------------------------------------------------------------------------- /app/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Revenue } from './definitions' 2 | 3 | export const formatCurrency = (amount: number) => 4 | (amount / 100).toLocaleString('en-US', { 5 | style: 'currency', 6 | currency: 'USD', 7 | }) 8 | 9 | export const formatDateToLocal = (dateStr: string, locale = 'en-US') => { 10 | const date = new Date(dateStr) 11 | const options: Intl.DateTimeFormatOptions = { 12 | day: 'numeric', 13 | month: 'short', 14 | year: 'numeric', 15 | } 16 | const formatter = new Intl.DateTimeFormat(locale, options) 17 | return formatter.format(date) 18 | } 19 | 20 | export const generateYAxis = (revenue: Revenue[]) => { 21 | // Calculate what labels we need to display on the y-axis 22 | // based on highest record and in 1000s 23 | const yAxisLabels = [] 24 | const highestRecord = Math.max(...revenue.map((month) => month.revenue)) 25 | const topLabel = Math.ceil(highestRecord / 1000) * 1000 26 | 27 | for (let i = topLabel; i >= 0; i -= 1000) { 28 | yAxisLabels.push(`$${i / 1000}K`) 29 | } 30 | 31 | return { yAxisLabels, topLabel } 32 | } 33 | 34 | export const generatePagination = (currentPage: number, totalPages: number) => { 35 | // If the total number of pages is 7 or less, 36 | // display all pages without any ellipsis. 37 | if (totalPages <= 7) { 38 | return Array.from({ length: totalPages }, (_, i) => i + 1) 39 | } 40 | 41 | // If the current page is among the first 3 pages, 42 | // show the first 3, an ellipsis, and the last 2 pages. 43 | if (currentPage <= 3) { 44 | return [1, 2, 3, '...', totalPages - 1, totalPages] 45 | } 46 | 47 | // If the current page is among the last 3 pages, 48 | // show the first 2, an ellipsis, and the last 3 pages. 49 | if (currentPage >= totalPages - 2) { 50 | return [1, 2, '...', totalPages - 2, totalPages - 1, totalPages] 51 | } 52 | 53 | // If the current page is somewhere in the middle, 54 | // show the first page, an ellipsis, the current page and its neighbors, 55 | // another ellipsis, and the last page. 56 | return [ 57 | 1, 58 | '...', 59 | currentPage - 1, 60 | currentPage, 61 | currentPage + 1, 62 | '...', 63 | totalPages, 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /app/login/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react' 2 | 3 | import { inter } from '../ui/fonts' 4 | import '../ui/global.css' 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | readonly children: ReactNode 10 | }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | 3 | import Link from 'next/link' 4 | 5 | import AcmeLogo from '@/app/ui/acme-logo' 6 | import LoginForm from '@/app/ui/login-form' 7 | 8 | export const metadata: Metadata = { 9 | title: 'Login | Acme Dashboard', 10 | } 11 | 12 | export default function LoginPage() { 13 | return ( 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import Link from 'next/link' 3 | 4 | import AcmeLogo from '@/app/ui/acme-logo' 5 | 6 | import { lusitana } from './ui/fonts' 7 | import styles from './ui/home.module.css' 8 | 9 | export default function Page() { 10 | return ( 11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 |

21 | Welcome to Acme. This is the example for the{' '} 22 | 23 | Next.js Learn Course 24 | 25 | , brought to you by Vercel. 26 |

27 | 31 | Log in 32 | 33 |
34 |
35 | Screenshots of the dashboard project showing desktop and mobile versions 42 | Screenshots of the dashboard project showing desktop and mobile versions 49 |
50 |
51 |
52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /app/ui/acme-logo.tsx: -------------------------------------------------------------------------------- 1 | import { GlobeAltIcon } from '@heroicons/react/24/outline' 2 | 3 | import { lusitana } from './fonts' 4 | 5 | export default function AcmeLogo() { 6 | return ( 7 |
10 | 11 |

Acme

12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /app/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import type { ButtonHTMLAttributes, ReactNode } from 'react' 3 | 4 | type ButtonProps = { 5 | readonly children: ReactNode 6 | } & ButtonHTMLAttributes 7 | 8 | export function Button({ children, className, ...rest }: ButtonProps) { 9 | return ( 10 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/ui/customers/table.tsx: -------------------------------------------------------------------------------- 1 | import { EyeIcon } from '@heroicons/react/24/outline' 2 | 3 | import Image from 'next/image' 4 | import Link from 'next/link' 5 | 6 | import type { FormattedCustomersTable } from '@/app/lib/definitions' 7 | import { lusitana } from '@/app/ui/fonts' 8 | import Search from '@/app/ui/search' 9 | 10 | export default async function CustomersTable({ 11 | customers, 12 | }: { 13 | customers: FormattedCustomersTable[] 14 | }) { 15 | return ( 16 |
17 |

18 | Customers 19 |

20 | 21 |
22 |
23 |
24 |
25 |
26 | {customers?.map((customer) => ( 27 |
31 |
32 |
33 | 34 |
35 |
36 | {`${customer.name}'s 43 |

{customer.name}

44 |
45 |
46 |

47 | {customer.email} 48 |

49 | 50 |
51 |
52 |
53 |
54 |

Pending

55 |

{customer.total_pending}

56 |
57 |
58 |

Paid

59 |

{customer.total_paid}

60 |
61 |
62 |
63 |

{customer.total_invoices} invoices

64 |
65 |
66 | ))} 67 |
68 | 69 | 70 | 71 | 74 | 77 | 80 | 83 | 86 | 89 | 90 | 91 | 92 | 93 | {customers.map((customer) => ( 94 | 95 | 107 | 110 | 113 | 116 | 119 | 125 | 126 | ))} 127 | 128 |
72 | Name 73 | 75 | Email 76 | 78 | Total Invoices 79 | 81 | Total Pending 82 | 84 | Total Paid 85 | 87 | Actions 88 |
96 |
97 | {`${customer.name}'s 104 |

{customer.name}

105 |
106 |
108 | {customer.email} 109 | 111 | {customer.total_invoices} 112 | 114 | {customer.total_pending} 115 | 117 | {customer.total_paid} 118 | 120 | 121 | View 122 | 123 | 124 |
129 |
130 |
131 |
132 |
133 |
134 | ) 135 | } 136 | -------------------------------------------------------------------------------- /app/ui/dashboard/cards.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BanknotesIcon, 3 | ClockIcon, 4 | InboxIcon, 5 | UserGroupIcon, 6 | } from '@heroicons/react/24/outline' 7 | 8 | import { fetchCardData } from '@/app/lib/data' 9 | import { lusitana } from '@/app/ui/fonts' 10 | 11 | const iconMap = { 12 | collected: BanknotesIcon, 13 | customers: UserGroupIcon, 14 | pending: ClockIcon, 15 | invoices: InboxIcon, 16 | } 17 | 18 | export default async function CardWrapper() { 19 | const { 20 | totalPaidInvoices, 21 | totalPendingInvoices, 22 | numberOfInvoices, 23 | numberOfCustomers, 24 | } = await fetchCardData() 25 | 26 | return ( 27 | <> 28 | 29 | 30 | 31 | 36 | 37 | ) 38 | } 39 | 40 | export function Card({ 41 | title, 42 | value, 43 | type, 44 | }: { 45 | readonly title: string 46 | readonly value: number | string 47 | readonly type: 'invoices' | 'customers' | 'pending' | 'collected' 48 | }) { 49 | const Icon = iconMap[type] 50 | 51 | return ( 52 |
53 |
54 | {Icon ? : null} 55 |

{title}

56 |
57 |

61 | {value} 62 |

63 |
64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /app/ui/dashboard/latest-invoices.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowPathIcon } from '@heroicons/react/24/outline' 2 | import clsx from 'clsx' 3 | 4 | import Image from 'next/image' 5 | 6 | import { fetchLatestInvoices } from '@/app/lib/data' 7 | import { lusitana } from '@/app/ui/fonts' 8 | 9 | export default async function LatestInvoices() { 10 | const latestInvoices = await fetchLatestInvoices() 11 | 12 | return ( 13 |
14 |

15 | Latest Invoices 16 |

17 |
18 |
19 | {latestInvoices.map((invoice, i) => ( 20 |
29 |
30 | {`${invoice.name}'s 37 |
38 |

39 | {invoice.name} 40 |

41 |

42 | {invoice.email} 43 |

44 |
45 |
46 |

49 | {invoice.amount} 50 |

51 |
52 | ))} 53 |
54 |
55 | 56 |

Updated just now

57 |
58 |
59 |
60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /app/ui/dashboard/nav-links.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { 4 | DocumentDuplicateIcon, 5 | HomeIcon, 6 | UserGroupIcon, 7 | } from '@heroicons/react/24/outline' 8 | import clsx from 'clsx' 9 | import Link from 'next/link' 10 | import { usePathname } from 'next/navigation' 11 | 12 | // Map of links to display in the side navigation. 13 | // Depending on the size of the application, this would be stored in a database. 14 | const links = [ 15 | { name: 'Home', href: '/dashboard', icon: HomeIcon }, 16 | { 17 | name: 'Invoices', 18 | href: '/dashboard/invoices', 19 | icon: DocumentDuplicateIcon, 20 | }, 21 | { name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon }, 22 | ] 23 | 24 | export default function NavLinks() { 25 | const pathname = usePathname() 26 | 27 | return ( 28 | <> 29 | {links.map((link) => { 30 | const LinkIcon = link.icon 31 | return ( 32 | 42 | 43 |

{link.name}

44 | 45 | ) 46 | })} 47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /app/ui/dashboard/revenue-chart.tsx: -------------------------------------------------------------------------------- 1 | import { CalendarIcon } from '@heroicons/react/24/outline' 2 | 3 | import { fetchRevenue } from '@/app/lib/data' 4 | import { generateYAxis } from '@/app/lib/utils' 5 | import { lusitana } from '@/app/ui/fonts' 6 | 7 | // This component is representational only. 8 | // For data visualization UI, check out: 9 | // https://www.tremor.so/ 10 | // https://www.chartjs.org/ 11 | // https://airbnb.io/visx/ 12 | 13 | export default async function RevenueChart() { 14 | const revenue = await fetchRevenue() 15 | const chartHeight = 350 16 | 17 | const { yAxisLabels, topLabel } = generateYAxis(revenue) 18 | 19 | if (!revenue || revenue.length === 0) { 20 | return

No data available.

21 | } 22 | 23 | return ( 24 |
25 |

26 | Recent Revenue 27 |

28 | 29 |
30 |
31 |
35 | {yAxisLabels.map((label) => ( 36 |

{label}

37 | ))} 38 |
39 | 40 | {revenue.map((month) => ( 41 |
42 |
48 |

49 | {month.month} 50 |

51 |
52 | ))} 53 |
54 |
55 | 56 |

Last 12 months

57 |
58 |
59 |
60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /app/ui/dashboard/sidenav.tsx: -------------------------------------------------------------------------------- 1 | import { PowerIcon } from '@heroicons/react/24/outline' 2 | 3 | import Link from 'next/link' 4 | 5 | import AcmeLogo from '@/app/ui/acme-logo' 6 | import NavLinks from '@/app/ui/dashboard/nav-links' 7 | import { signOut } from '@/auth' 8 | 9 | export default function SideNav() { 10 | return ( 11 |
12 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
{ 25 | 'use server' 26 | await signOut() 27 | }} 28 | > 29 | 36 |
37 |
38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /app/ui/fonts.ts: -------------------------------------------------------------------------------- 1 | import { Inter, Lusitana } from 'next/font/google' 2 | 3 | export const inter = Inter({ subsets: ['latin'] }) 4 | 5 | export const lusitana = Lusitana({ 6 | weight: '400', 7 | subsets: ['latin'], 8 | }) 9 | -------------------------------------------------------------------------------- /app/ui/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | input[type="number"] { 6 | -moz-appearance: textfield; 7 | appearance: textfield; 8 | } 9 | 10 | input[type="number"]::-webkit-inner-spin-button { 11 | -webkit-appearance: none; 12 | margin: 0; 13 | } 14 | 15 | input[type="number"]::-webkit-outer-spin-button { 16 | -webkit-appearance: none; 17 | margin: 0; 18 | } 19 | -------------------------------------------------------------------------------- /app/ui/home.module.css: -------------------------------------------------------------------------------- 1 | .shape { 2 | height: 0; 3 | width: 0; 4 | border-bottom: 30px solid black; 5 | border-left: 20px solid transparent; 6 | border-right: 20px solid transparent; 7 | } 8 | -------------------------------------------------------------------------------- /app/ui/invoices/breadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from 'clsx' 2 | 3 | import Link from 'next/link' 4 | 5 | import { lusitana } from '@/app/ui/fonts' 6 | 7 | type Breadcrumb = { 8 | label: string 9 | href: string 10 | active?: boolean 11 | } 12 | 13 | export default function Breadcrumbs({ 14 | breadcrumbs, 15 | }: { 16 | readonly breadcrumbs: Breadcrumb[] 17 | }) { 18 | return ( 19 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /app/ui/invoices/buttons.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-no-bind */ 2 | import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline' 3 | 4 | import Link from 'next/link' 5 | 6 | import { deleteInvoice } from '@/app/lib/actions' 7 | 8 | export function CreateInvoice() { 9 | return ( 10 | 14 | Create Invoice{' '} 15 | 16 | 17 | ) 18 | } 19 | 20 | export function UpdateInvoice({ id }: { readonly id: string }) { 21 | return ( 22 | 26 | 27 | 28 | ) 29 | } 30 | 31 | export function DeleteInvoice({ id }: { readonly id: string }) { 32 | const deleteInvoiceWithId = deleteInvoice.bind(null, id) 33 | 34 | return ( 35 |
36 | 40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /app/ui/invoices/create-form.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { 4 | CheckIcon, 5 | ClockIcon, 6 | CurrencyDollarIcon, 7 | UserCircleIcon, 8 | } from '@heroicons/react/24/outline' 9 | import type { Metadata } from 'next' 10 | import { useFormState } from 'react-dom' 11 | 12 | import Link from 'next/link' 13 | 14 | import { createInvoice } from '@/app/lib/actions' 15 | import type { CustomerField } from '@/app/lib/definitions' 16 | import { Button } from '@/app/ui/button' 17 | 18 | export const metadata: Metadata = { 19 | title: 'Create Invoice | Acme Dashboard', 20 | } 21 | 22 | export default function CreateForm({ 23 | customers, 24 | }: { 25 | readonly customers: CustomerField[] 26 | }) { 27 | const initialState = { message: null, errors: {} } 28 | // @ts-expect-error this is beta feature 29 | const [state, dispatch] = useFormState(createInvoice, initialState) 30 | 31 | return ( 32 |
33 |
34 | {/* Customer Name */} 35 |
36 | 39 |
40 | 56 | 57 |
58 | 59 | {state.errors?.customerId ? ( 60 |
65 | {state.errors.customerId.map((error: string) => ( 66 |

{error}

67 | ))} 68 |
69 | ) : null} 70 |
71 | 72 | {/* Invoice Amount */} 73 |
74 | 77 |
78 |
79 | 88 | 89 |
90 |
91 | 92 | {state.errors?.amount ? ( 93 |
98 | {state.errors.amount.map((error: string) => ( 99 |

{error}

100 | ))} 101 |
102 | ) : null} 103 |
104 | 105 | {/* Invoice Status */} 106 |
107 | 108 | Set the invoice status 109 | 110 |
111 |
112 |
113 | 121 | 127 |
128 |
129 | 137 | 143 |
144 | 145 | {state.errors?.status ? ( 146 |
151 | {state.errors.status.map((error: string) => ( 152 |

{error}

153 | ))} 154 |
155 | ) : null} 156 |
157 |
158 |
159 |
160 |
161 | 165 | Cancel 166 | 167 | 168 |
169 |
170 | ) 171 | } 172 | -------------------------------------------------------------------------------- /app/ui/invoices/edit-form.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { 4 | CheckIcon, 5 | ClockIcon, 6 | CurrencyDollarIcon, 7 | UserCircleIcon, 8 | } from '@heroicons/react/24/outline' 9 | 10 | import Link from 'next/link' 11 | 12 | import { updateInvoice } from '@/app/lib/actions' 13 | import type { CustomerField, InvoiceForm } from '@/app/lib/definitions' 14 | import { Button } from '@/app/ui/button' 15 | 16 | /* eslint-disable react/jsx-no-bind */ 17 | 18 | export default function EditInvoiceForm({ 19 | invoice, 20 | customers, 21 | }: { 22 | readonly invoice: InvoiceForm 23 | readonly customers: CustomerField[] 24 | }) { 25 | const updateInvoiceWithId = updateInvoice.bind(null, invoice.id) 26 | 27 | return ( 28 |
29 |
30 | {/* Customer Name */} 31 |
32 | 35 |
36 | 52 | 53 |
54 |
55 | 56 | {/* Invoice Amount */} 57 |
58 | 61 |
62 |
63 | 72 | 73 |
74 |
75 |
76 | 77 | {/* Invoice Status */} 78 |
79 | 80 | Set the invoice status 81 | 82 |
83 |
84 |
85 | 94 | 100 |
101 |
102 | 111 | 117 |
118 |
119 |
120 |
121 |
122 |
123 | 127 | Cancel 128 | 129 | 130 |
131 |
132 | ) 133 | } 134 | -------------------------------------------------------------------------------- /app/ui/invoices/pagination.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline' 4 | import clsx from 'clsx' 5 | 6 | import Link from 'next/link' 7 | import { usePathname, useSearchParams } from 'next/navigation' 8 | 9 | import { generatePagination } from '@/app/lib/utils' 10 | 11 | export default function Pagination({ 12 | totalPages, 13 | }: { readonly totalPages: number }) { 14 | const pathname = usePathname() 15 | const searchParams = useSearchParams() 16 | const currentPage = Number(searchParams.get('page')) || 1 17 | 18 | const allPages = generatePagination(currentPage, totalPages) 19 | 20 | const createPageURL = (pageNumber: number | string) => { 21 | const params = new URLSearchParams(searchParams) 22 | params.set('page', pageNumber.toString()) 23 | return `${pathname}?${params.toString()}` 24 | } 25 | 26 | return ( 27 |
28 | 33 | 34 |
35 | {allPages.map((page, index) => { 36 | let position: 'first' | 'last' | 'single' | 'middle' | undefined 37 | 38 | if (index === 0) { 39 | position = 'first' 40 | } 41 | if (index === allPages.length - 1) { 42 | position = 'last' 43 | } 44 | if (allPages.length === 1) { 45 | position = 'single' 46 | } 47 | if (page === '...') { 48 | position = 'middle' 49 | } 50 | 51 | return ( 52 | 59 | ) 60 | })} 61 |
62 | 63 | = totalPages} 67 | /> 68 |
69 | ) 70 | } 71 | 72 | function PaginationNumber({ 73 | page, 74 | href, 75 | isActive, 76 | position, 77 | }: { 78 | readonly page: number | string 79 | readonly href: string 80 | readonly position?: 'first' | 'last' | 'middle' | 'single' 81 | readonly isActive: boolean 82 | }) { 83 | const className = clsx( 84 | 'flex h-10 w-10 items-center justify-center text-sm border', 85 | { 86 | 'rounded-l-md': position === 'first' || position === 'single', 87 | 'rounded-r-md': position === 'last' || position === 'single', 88 | 'z-10 bg-blue-600 border-blue-600 text-white': isActive, 89 | 'hover:bg-gray-100': !isActive && position !== 'middle', 90 | 'text-gray-300': position === 'middle', 91 | }, 92 | ) 93 | 94 | return isActive || position === 'middle' ? ( 95 |
{page}
96 | ) : ( 97 | 98 | {page} 99 | 100 | ) 101 | } 102 | 103 | function PaginationArrow({ 104 | href, 105 | direction, 106 | isDisabled, 107 | }: { 108 | readonly href: string 109 | readonly direction: 'left' | 'right' 110 | readonly isDisabled?: boolean 111 | }) { 112 | const className = clsx( 113 | 'flex h-10 w-10 items-center justify-center rounded-md border', 114 | { 115 | 'pointer-events-none text-gray-300': isDisabled, 116 | 'hover:bg-gray-100': !isDisabled, 117 | 'mr-2 md:mr-4': direction === 'left', 118 | 'ml-2 md:ml-4': direction === 'right', 119 | }, 120 | ) 121 | 122 | const icon = 123 | direction === 'left' ? ( 124 | 125 | ) : ( 126 | 127 | ) 128 | 129 | return isDisabled ? ( 130 |
{icon}
131 | ) : ( 132 | 133 | {icon} 134 | 135 | ) 136 | } 137 | -------------------------------------------------------------------------------- /app/ui/invoices/status.tsx: -------------------------------------------------------------------------------- 1 | import { CheckIcon, ClockIcon } from '@heroicons/react/24/outline' 2 | import clsx from 'clsx' 3 | 4 | export default function InvoiceStatus({ status }: { readonly status: string }) { 5 | return ( 6 | 15 | {status === 'pending' ? ( 16 | <> 17 | Pending 18 | 19 | 20 | ) : null} 21 | {status === 'paid' ? ( 22 | <> 23 | Paid 24 | 25 | 26 | ) : null} 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /app/ui/invoices/table.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | import { fetchFilteredInvoices } from '@/app/lib/data' 4 | import { formatCurrency, formatDateToLocal } from '@/app/lib/utils' 5 | import { DeleteInvoice, UpdateInvoice } from '@/app/ui/invoices/buttons' 6 | import InvoiceStatus from '@/app/ui/invoices/status' 7 | 8 | export default async function InvoicesTable({ 9 | query, 10 | currentPage, 11 | }: { 12 | query: string 13 | currentPage: number 14 | }) { 15 | const invoices = await fetchFilteredInvoices(query, currentPage) 16 | 17 | return ( 18 |
19 |
20 |
21 |
22 | {invoices?.map((invoice) => ( 23 |
27 |
28 |
29 |
30 | {`${invoice.name}'s 37 |

{invoice.name}

38 |
39 |

{invoice.email}

40 |
41 | 42 |
43 |
44 |
45 |

46 | {formatCurrency(invoice.amount)} 47 |

48 |

{formatDateToLocal(invoice.date)}

49 |
50 |
51 | 52 | 53 |
54 |
55 |
56 | ))} 57 |
58 | 59 | 60 | 61 | 64 | 67 | 70 | 73 | 76 | 79 | 80 | 81 | 82 | {invoices?.map((invoice) => ( 83 | 87 | 99 | 102 | 105 | 108 | 111 | 117 | 118 | ))} 119 | 120 |
62 | Customer 63 | 65 | Email 66 | 68 | Amount 69 | 71 | Date 72 | 74 | Status 75 | 77 | Edit 78 |
88 |
89 | {`${invoice.name}'s 96 |

{invoice.name}

97 |
98 |
100 | {invoice.email} 101 | 103 | {formatCurrency(invoice.amount)} 104 | 106 | {formatDateToLocal(invoice.date)} 107 | 109 | 110 | 112 |
113 | 114 | 115 |
116 |
121 |
122 |
123 |
124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /app/ui/login-form.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ArrowRightIcon } from '@heroicons/react/20/solid' 4 | import { 5 | AtSymbolIcon, 6 | ExclamationCircleIcon, 7 | KeyIcon, 8 | } from '@heroicons/react/24/outline' 9 | import { useFormState, useFormStatus } from 'react-dom' 10 | 11 | import { authenticate } from '@/app/lib/actions' 12 | import { lusitana } from '@/app/ui/fonts' 13 | 14 | import { Button } from './button' 15 | 16 | export default function LoginForm() { 17 | const [state, dispatch] = useFormState(authenticate, undefined) 18 | 19 | return ( 20 |
21 |
22 |

23 | Please log in to continue. 24 |

25 |
26 |
27 | 33 |
34 | 42 | 43 |
44 |
45 |
46 | 52 |
53 | 62 | 63 |
64 |
65 |
66 | 67 |
68 | {state === 'CredentialSignin' && ( 69 | <> 70 | 71 |

72 | Invalid credentials. Please try again. 73 |

74 | 75 | )} 76 |
77 | 78 |

79 | Hints: demo account is user@nextmail.com and password is{' '} 80 | 123456. 81 |

82 |
83 |
84 | ) 85 | } 86 | 87 | function LoginButton() { 88 | const { pending } = useFormStatus() 89 | 90 | return ( 91 | 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /app/ui/search.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { MagnifyingGlassIcon } from '@heroicons/react/24/outline' 4 | import { useDebouncedCallback } from 'use-debounce' 5 | 6 | import { usePathname, useRouter, useSearchParams } from 'next/navigation' 7 | 8 | import logger from '../lib/logger' 9 | 10 | export default function Search({ 11 | placeholder, 12 | }: { readonly placeholder: string }) { 13 | const searchParams = useSearchParams() 14 | const pathname = usePathname() 15 | const { replace } = useRouter() 16 | 17 | const handleSearch = useDebouncedCallback((keyword: string) => { 18 | logger.info('Searching for', keyword) 19 | const params = new URLSearchParams(searchParams) 20 | params.set('page', '1') // Reset page to 1 21 | 22 | if (keyword) { 23 | params.set('query', keyword) 24 | } else { 25 | params.delete('query') 26 | } 27 | 28 | replace(`${pathname}?${params.toString()}`) 29 | }, 300) 30 | 31 | return ( 32 |
33 | 36 | handleSearch(e.target.value)} 41 | /> 42 | 43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /app/ui/skeletons.tsx: -------------------------------------------------------------------------------- 1 | // Loading animation 2 | const shimmer = 3 | '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' 4 | 5 | export function CardSkeleton() { 6 | return ( 7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ) 19 | } 20 | 21 | export function CardsSkeleton() { 22 | return ( 23 | <> 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export function RevenueChartSkeleton() { 33 | return ( 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | export function InvoiceSkeleton() { 48 | return ( 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ) 60 | } 61 | 62 | export function LatestInvoicesSkeleton() { 63 | return ( 64 |
67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | ) 83 | } 84 | 85 | export default function DashboardSkeleton() { 86 | return ( 87 | <> 88 |
91 |
92 | 93 | 94 | 95 | 96 |
97 |
98 | 99 | 100 |
101 | 102 | ) 103 | } 104 | 105 | export function TableRowSkeleton() { 106 | return ( 107 | 108 | {/* Customer Name and Image */} 109 | 110 |
111 |
112 |
113 |
114 | 115 | {/* Email */} 116 | 117 |
118 | 119 | {/* Amount */} 120 | 121 |
122 | 123 | {/* Date */} 124 | 125 |
126 | 127 | {/* Status */} 128 | 129 |
130 | 131 | {/* Actions */} 132 | 133 |
134 |
135 |
136 |
137 | 138 | 139 | ) 140 | } 141 | 142 | export function InvoicesMobileSkeleton() { 143 | return ( 144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | ) 164 | } 165 | 166 | export function InvoicesTableSkeleton() { 167 | return ( 168 |
169 |
170 |
171 |
172 | 173 | 174 | 175 | 176 | 177 | 178 |
179 | 180 | 181 | 182 | 185 | 188 | 191 | 194 | 197 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 |
183 | Customer 184 | 186 | Email 187 | 189 | Amount 190 | 192 | Date 193 | 195 | Status 196 | 201 | Edit 202 |
214 |
215 |
216 |
217 | ) 218 | } 219 | -------------------------------------------------------------------------------- /auth.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextAuthConfig } from 'next-auth' 2 | 3 | export const authConfig = { 4 | providers: [], 5 | pages: { 6 | signIn: '/login', 7 | }, 8 | callbacks: { 9 | authorized({ auth, request: { nextUrl } }) { 10 | const isLoggedIn = Boolean(auth?.user) 11 | const isOnDashboard = nextUrl.pathname.startsWith('/dashboard') 12 | if (isOnDashboard) { 13 | if (isLoggedIn) { 14 | return true 15 | } 16 | return false // Redirect unauthenticated users to login page 17 | } 18 | 19 | if (isLoggedIn) { 20 | return Response.redirect(new URL('/dashboard', nextUrl)) 21 | } 22 | 23 | return true 24 | }, 25 | }, 26 | } satisfies NextAuthConfig 27 | -------------------------------------------------------------------------------- /auth.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt' 2 | import NextAuth from 'next-auth' 3 | import Credentials from 'next-auth/providers/credentials' 4 | import { z } from 'zod' 5 | 6 | import { getUser } from './app/lib/data' 7 | import logger from './app/lib/logger' 8 | import { authConfig } from './auth.config' 9 | 10 | export const { auth, signIn, signOut } = NextAuth({ 11 | ...authConfig, 12 | providers: [ 13 | Credentials({ 14 | async authorize(credentials) { 15 | const parsedCredentials = z 16 | .object({ email: z.string().email(), password: z.string().min(6) }) 17 | .safeParse(credentials) 18 | 19 | if (parsedCredentials.success) { 20 | const { email, password } = parsedCredentials.data 21 | const user = await getUser(email) 22 | if (!user) { 23 | return null 24 | } 25 | 26 | const passwordsMatch = await bcrypt.compare(password, user.password) 27 | if (passwordsMatch) { 28 | return user 29 | } 30 | } else { 31 | logger.error(parsedCredentials.error) 32 | } 33 | 34 | return null 35 | }, 36 | }), 37 | ], 38 | }) 39 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", 3 | "vcs": { 4 | "clientKind": "git", 5 | "enabled": true, 6 | "useIgnoreFile": true, 7 | "defaultBranch": "main" 8 | }, 9 | "formatter": { 10 | "enabled": true, 11 | "formatWithErrors": false, 12 | "indentStyle": "space", 13 | "indentWidth": 2, 14 | "lineEnding": "lf", 15 | "lineWidth": 80, 16 | "attributePosition": "auto" 17 | }, 18 | "organizeImports": { "enabled": true }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": false, 23 | "a11y": { "noBlankTarget": "error", "useButtonType": "error" }, 24 | "complexity": { 25 | "noBannedTypes": "error", 26 | "noExtraBooleanCast": "error", 27 | "noMultipleSpacesInRegularExpressionLiterals": "error", 28 | "noStaticOnlyClass": "error", 29 | "noUselessCatch": "error", 30 | "noUselessConstructor": "error", 31 | "noUselessEmptyExport": "error", 32 | "noUselessFragments": "error", 33 | "noUselessLabel": "error", 34 | "noUselessLoneBlockStatements": "error", 35 | "noUselessRename": "error", 36 | "noUselessTernary": "error", 37 | "noUselessThisAlias": "error", 38 | "noUselessTypeConstraint": "error", 39 | "noVoid": "error", 40 | "noWith": "error", 41 | "useArrowFunction": "error", 42 | "useLiteralKeys": "error", 43 | "useOptionalChain": "error", 44 | "useRegexLiterals": "error" 45 | }, 46 | "correctness": { 47 | "noChildrenProp": "error", 48 | "noConstAssign": "error", 49 | "noConstantCondition": "error", 50 | "noConstructorReturn": "error", 51 | "noEmptyCharacterClassInRegex": "error", 52 | "noEmptyPattern": "error", 53 | "noGlobalObjectCalls": "error", 54 | "noInnerDeclarations": "error", 55 | "noInvalidConstructorSuper": "error", 56 | "noInvalidNewBuiltin": "error", 57 | "noNonoctalDecimalEscape": "error", 58 | "noPrecisionLoss": "error", 59 | "noSelfAssign": "error", 60 | "noSetterReturn": "error", 61 | "noSwitchDeclarations": "error", 62 | "noUndeclaredVariables": "error", 63 | "noUnreachable": "error", 64 | "noUnreachableSuper": "error", 65 | "noUnsafeFinally": "error", 66 | "noUnsafeOptionalChaining": "error", 67 | "noUnusedLabels": "error", 68 | "noUnusedVariables": "error", 69 | "noVoidElementsWithChildren": "error", 70 | "useExhaustiveDependencies": "warn", 71 | "useHookAtTopLevel": "error", 72 | "useIsNan": "error", 73 | "useJsxKeyInIterable": "error", 74 | "useValidForDirection": "error", 75 | "useYield": "error" 76 | }, 77 | "security": { 78 | "noDangerouslySetInnerHtml": "error", 79 | "noDangerouslySetInnerHtmlWithChildren": "error", 80 | "noGlobalEval": "error" 81 | }, 82 | "style": { 83 | "noArguments": "error", 84 | "noCommaOperator": "error", 85 | "noImplicitBoolean": "error", 86 | "noInferrableTypes": "error", 87 | "noNamespace": "error", 88 | "noNegationElse": "error", 89 | "noParameterProperties": "error", 90 | "noRestrictedGlobals": { 91 | "level": "error", 92 | "options": { "deniedGlobals": ["event", "atob", "btoa"] } 93 | }, 94 | "noUselessElse": "error", 95 | "noVar": "error", 96 | "useAsConstAssertion": "error", 97 | "useBlockStatements": "error", 98 | "useCollapsedElseIf": "error", 99 | "useConsistentArrayType": { 100 | "level": "error", 101 | "options": { "syntax": "shorthand" } 102 | }, 103 | "useConst": "error", 104 | "useDefaultParameterLast": "error", 105 | "useExponentiationOperator": "error", 106 | "useExportType": "error", 107 | "useForOf": "error", 108 | "useFragmentSyntax": "error", 109 | "useImportType": "error", 110 | "useLiteralEnumMembers": "error", 111 | "useNamingConvention": { 112 | "level": "off", 113 | "options": { "strictCase": false } 114 | }, 115 | "useNumericLiterals": "error", 116 | "useShorthandAssign": "error", 117 | "useShorthandFunctionType": "error", 118 | "useSingleVarDeclarator": "error" 119 | }, 120 | "suspicious": { 121 | "noArrayIndexKey": "error", 122 | "noAssignInExpressions": "error", 123 | "noAsyncPromiseExecutor": "error", 124 | "noCatchAssign": "error", 125 | "noClassAssign": "error", 126 | "noCommentText": "error", 127 | "noCompareNegZero": "error", 128 | "noConfusingLabels": "error", 129 | "noControlCharactersInRegex": "error", 130 | "noDebugger": "error", 131 | "noDoubleEquals": "error", 132 | "noDuplicateCase": "error", 133 | "noDuplicateClassMembers": "error", 134 | "noDuplicateJsxProps": "error", 135 | "noDuplicateObjectKeys": "error", 136 | "noDuplicateParameters": "error", 137 | "noEmptyBlockStatements": "error", 138 | "noEmptyInterface": "error", 139 | "noExtraNonNullAssertion": "error", 140 | "noFallthroughSwitchClause": "error", 141 | "noFunctionAssign": "error", 142 | "noGlobalAssign": "error", 143 | "noImportAssign": "error", 144 | "noLabelVar": "error", 145 | "noMisleadingCharacterClass": "error", 146 | "noMisleadingInstantiator": "error", 147 | "noPrototypeBuiltins": "error", 148 | "noRedeclare": "error", 149 | "noSelfCompare": "error", 150 | "noShadowRestrictedNames": "error", 151 | "noUnsafeDeclarationMerging": "error", 152 | "noUnsafeNegation": "error", 153 | "useDefaultSwitchClauseLast": "error", 154 | "useGetterReturn": "error", 155 | "useNamespaceKeyword": "error", 156 | "useValidTypeof": "error" 157 | } 158 | }, 159 | "ignore": ["dist", ".eslintrc", "next.config.js"] 160 | }, 161 | "javascript": { 162 | "formatter": { 163 | "jsxQuoteStyle": "double", 164 | "quoteProperties": "asNeeded", 165 | "trailingComma": "all", 166 | "semicolons": "asNeeded", 167 | "arrowParentheses": "always", 168 | "bracketSpacing": true, 169 | "bracketSameLine": false, 170 | "quoteStyle": "single", 171 | "attributePosition": "auto" 172 | } 173 | }, 174 | "overrides": [ 175 | { 176 | "include": ["**/*.d.ts"], 177 | "linter": { "rules": { "correctness": { "noUnusedVariables": "off" } } } 178 | }, 179 | { "include": ["**/*.test-d.ts"], "linter": { "rules": {} } }, 180 | { 181 | "include": ["**/*.tsx"], 182 | "linter": { 183 | "rules": { 184 | "style": { 185 | "useNamingConvention": { 186 | "level": "error", 187 | "options": { "strictCase": false } 188 | } 189 | } 190 | } 191 | } 192 | } 193 | ] 194 | } 195 | -------------------------------------------------------------------------------- /cspell-tool.txt: -------------------------------------------------------------------------------- 1 | sidenav 2 | ILIKE 3 | Delba 4 | Oliveira 5 | delba 6 | oliveira 7 | Steph 8 | Dietz 9 | steph 10 | dietz 11 | Novotny 12 | novotny 13 | Kowalski 14 | kowalski 15 | Balazs 16 | Orban 17 | balazs 18 | orban 19 | Lusitana 20 | lusitana 21 | Nonoctal 22 | Instantiator 23 | Prerendering 24 | typecheck 25 | biomejs 26 | trivago 27 | heroicons 28 | tailwindcss 29 | vercel 30 | autoprefixer 31 | clsx 32 | pino 33 | postcss 34 | dotenv 35 | alloc 36 | simplewebauthn 37 | nodemailer 38 | isaacs 39 | jridgewell 40 | mapbox 41 | neondatabase 42 | nodelib 43 | panva 44 | pkgjs 45 | opentelemetry 46 | preact 47 | iojs 48 | browserslist 49 | bufferutil 50 | hkdf 51 | webapi 52 | picocolors 53 | jsesc 54 | libc 55 | nopt 56 | npmlog 57 | rimraf 58 | scandir 59 | fastq 60 | undici 61 | csstype 62 | picomatch 63 | concat 64 | streamsearch 65 | anymatch 66 | readdirp 67 | fsevents 68 | reusify 69 | minipass 70 | aproba 71 | jackspeak 72 | minimatch 73 | realpath 74 | wrappy 75 | hasown 76 | extglob 77 | cliui 78 | parseargs 79 | yallist 80 | thenify 81 | msvc 82 | whatwg 83 | bytea 84 | camelcase 85 | lilconfig 86 | cssesc 87 | xtend 88 | envify 89 | pify 90 | microtask 91 | fullwidth 92 | eastasianwidth 93 | chokidar 94 | didyoumean 95 | jiti 96 | chownr 97 | minizlib 98 | mkdirp 99 | webidl 100 | isexe 101 | automerge 102 | ossp 103 | docstrings 104 | sweepai 105 | pygithub 106 | sandboxed -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "version": "0.2", 4 | "language": "en", 5 | "globRoot": ".", 6 | "dictionaryDefinitions": [ 7 | { 8 | "name": "cspell-tool", 9 | "path": "./cspell-tool.txt", 10 | "addWords": true 11 | } 12 | ], 13 | "dictionaries": ["cspell-tool"], 14 | "ignorePaths": ["node_modules", "dist", "build", "/cspell-tool.txt"] 15 | } 16 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth' 2 | 3 | import { authConfig } from './auth.config' 4 | 5 | export default NextAuth(authConfig).auth 6 | 7 | export const config = { 8 | // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher 9 | matcher: ['/((?!api|static|.*\\..*|_next).*)'], 10 | } 11 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-nextjs14", 3 | "private": true, 4 | "scripts": { 5 | "build": "next build", 6 | "dev": "next dev", 7 | "lint": "biome lint --apply .", 8 | "format": "biome format --write .", 9 | "seed": "npx tsx ./scripts/seed.ts", 10 | "start": "next start", 11 | "typecheck": "tsc --noEmit" 12 | }, 13 | "dependencies": { 14 | "@heroicons/react": "2.1.5", 15 | "@tailwindcss/forms": "0.5.9", 16 | "@types/node": "22.10.1", 17 | "@vercel/postgres": "0.10.0", 18 | "autoprefixer": "10.4.20", 19 | "bcrypt": "5.1.1", 20 | "clsx": "2.1.1", 21 | "next": "14.2.13", 22 | "next-auth": "5.0.0-beta.21", 23 | "pino": "9.4.0", 24 | "postcss": "8.4.47", 25 | "react": "18.3.1", 26 | "react-dom": "18.3.1", 27 | "tailwindcss": "3.4.13", 28 | "typescript": "5.6.2", 29 | "use-debounce": "10.0.3", 30 | "zod": "3.23.8" 31 | }, 32 | "devDependencies": { 33 | "@biomejs/biome": "1.9.2", 34 | "@trivago/prettier-plugin-sort-imports": "4.3.0", 35 | "@types/bcrypt": "5.0.2", 36 | "@types/react": "18.3.8", 37 | "@types/react-dom": "18.3.0", 38 | "dotenv": "16.4.5" 39 | }, 40 | "engines": { 41 | "node": ">=18" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@heroicons/react': 12 | specifier: 2.1.5 13 | version: 2.1.5(react@18.3.1) 14 | '@tailwindcss/forms': 15 | specifier: 0.5.9 16 | version: 0.5.9(tailwindcss@3.4.13) 17 | '@types/node': 18 | specifier: 22.10.1 19 | version: 22.10.1 20 | '@vercel/postgres': 21 | specifier: 0.10.0 22 | version: 0.10.0 23 | autoprefixer: 24 | specifier: 10.4.20 25 | version: 10.4.20(postcss@8.4.47) 26 | bcrypt: 27 | specifier: 5.1.1 28 | version: 5.1.1 29 | clsx: 30 | specifier: 2.1.1 31 | version: 2.1.1 32 | next: 33 | specifier: 14.2.13 34 | version: 14.2.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 35 | next-auth: 36 | specifier: 5.0.0-beta.21 37 | version: 5.0.0-beta.21(next@14.2.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) 38 | pino: 39 | specifier: 9.4.0 40 | version: 9.4.0 41 | postcss: 42 | specifier: 8.4.47 43 | version: 8.4.47 44 | react: 45 | specifier: 18.3.1 46 | version: 18.3.1 47 | react-dom: 48 | specifier: 18.3.1 49 | version: 18.3.1(react@18.3.1) 50 | tailwindcss: 51 | specifier: 3.4.13 52 | version: 3.4.13 53 | typescript: 54 | specifier: 5.6.2 55 | version: 5.6.2 56 | use-debounce: 57 | specifier: 10.0.3 58 | version: 10.0.3(react@18.3.1) 59 | zod: 60 | specifier: 3.23.8 61 | version: 3.23.8 62 | devDependencies: 63 | '@biomejs/biome': 64 | specifier: 1.9.2 65 | version: 1.9.2 66 | '@trivago/prettier-plugin-sort-imports': 67 | specifier: 4.3.0 68 | version: 4.3.0(prettier@3.3.3) 69 | '@types/bcrypt': 70 | specifier: 5.0.2 71 | version: 5.0.2 72 | '@types/react': 73 | specifier: 18.3.8 74 | version: 18.3.8 75 | '@types/react-dom': 76 | specifier: 18.3.0 77 | version: 18.3.0 78 | dotenv: 79 | specifier: 16.4.5 80 | version: 16.4.5 81 | 82 | packages: 83 | 84 | '@alloc/quick-lru@5.2.0': 85 | resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} 86 | engines: {node: '>=10'} 87 | 88 | '@auth/core@0.35.0': 89 | resolution: {integrity: sha512-XvMALiYn5ZQd1hVeG1t+jCU89jRrc7ortl/05wkBrPHnRWZScxAK5jKuzBz+AOBQXewDjYcMpzeF5tTqg6rDhQ==} 90 | peerDependencies: 91 | '@simplewebauthn/browser': ^9.0.1 92 | '@simplewebauthn/server': ^9.0.2 93 | nodemailer: ^6.8.0 94 | peerDependenciesMeta: 95 | '@simplewebauthn/browser': 96 | optional: true 97 | '@simplewebauthn/server': 98 | optional: true 99 | nodemailer: 100 | optional: true 101 | 102 | '@babel/code-frame@7.26.2': 103 | resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 104 | engines: {node: '>=6.9.0'} 105 | 106 | '@babel/generator@7.17.7': 107 | resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} 108 | engines: {node: '>=6.9.0'} 109 | 110 | '@babel/generator@7.26.2': 111 | resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} 112 | engines: {node: '>=6.9.0'} 113 | 114 | '@babel/helper-environment-visitor@7.24.7': 115 | resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} 116 | engines: {node: '>=6.9.0'} 117 | 118 | '@babel/helper-function-name@7.24.7': 119 | resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} 120 | engines: {node: '>=6.9.0'} 121 | 122 | '@babel/helper-hoist-variables@7.24.7': 123 | resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} 124 | engines: {node: '>=6.9.0'} 125 | 126 | '@babel/helper-split-export-declaration@7.24.7': 127 | resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} 128 | engines: {node: '>=6.9.0'} 129 | 130 | '@babel/helper-string-parser@7.25.9': 131 | resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 132 | engines: {node: '>=6.9.0'} 133 | 134 | '@babel/helper-validator-identifier@7.25.9': 135 | resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 136 | engines: {node: '>=6.9.0'} 137 | 138 | '@babel/parser@7.26.2': 139 | resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} 140 | engines: {node: '>=6.0.0'} 141 | hasBin: true 142 | 143 | '@babel/template@7.25.9': 144 | resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} 145 | engines: {node: '>=6.9.0'} 146 | 147 | '@babel/traverse@7.23.2': 148 | resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} 149 | engines: {node: '>=6.9.0'} 150 | 151 | '@babel/types@7.17.0': 152 | resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} 153 | engines: {node: '>=6.9.0'} 154 | 155 | '@babel/types@7.26.0': 156 | resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} 157 | engines: {node: '>=6.9.0'} 158 | 159 | '@biomejs/biome@1.9.2': 160 | resolution: {integrity: sha512-4j2Gfwft8Jqp1X0qLYvK4TEy4xhTo4o6rlvJPsjPeEame8gsmbGQfOPBkw7ur+7/Z/f0HZmCZKqbMvR7vTXQYQ==} 161 | engines: {node: '>=14.21.3'} 162 | hasBin: true 163 | 164 | '@biomejs/cli-darwin-arm64@1.9.2': 165 | resolution: {integrity: sha512-rbs9uJHFmhqB3Td0Ro+1wmeZOHhAPTL3WHr8NtaVczUmDhXkRDWScaxicG9+vhSLj1iLrW47itiK6xiIJy6vaA==} 166 | engines: {node: '>=14.21.3'} 167 | cpu: [arm64] 168 | os: [darwin] 169 | 170 | '@biomejs/cli-darwin-x64@1.9.2': 171 | resolution: {integrity: sha512-BlfULKijNaMigQ9GH9fqJVt+3JTDOSiZeWOQtG/1S1sa8Lp046JHG3wRJVOvekTPL9q/CNFW1NVG8J0JN+L1OA==} 172 | engines: {node: '>=14.21.3'} 173 | cpu: [x64] 174 | os: [darwin] 175 | 176 | '@biomejs/cli-linux-arm64-musl@1.9.2': 177 | resolution: {integrity: sha512-ZATvbUWhNxegSALUnCKWqetTZqrK72r2RsFD19OK5jXDj/7o1hzI1KzDNG78LloZxftrwr3uI9SqCLh06shSZw==} 178 | engines: {node: '>=14.21.3'} 179 | cpu: [arm64] 180 | os: [linux] 181 | 182 | '@biomejs/cli-linux-arm64@1.9.2': 183 | resolution: {integrity: sha512-T8TJuSxuBDeQCQzxZu2o3OU4eyLumTofhCxxFd3+aH2AEWVMnH7Z/c3QP1lHI5RRMBP9xIJeMORqDQ5j+gVZzw==} 184 | engines: {node: '>=14.21.3'} 185 | cpu: [arm64] 186 | os: [linux] 187 | 188 | '@biomejs/cli-linux-x64-musl@1.9.2': 189 | resolution: {integrity: sha512-CjPM6jT1miV5pry9C7qv8YJk0FIZvZd86QRD3atvDgfgeh9WQU0k2Aoo0xUcPdTnoz0WNwRtDicHxwik63MmSg==} 190 | engines: {node: '>=14.21.3'} 191 | cpu: [x64] 192 | os: [linux] 193 | 194 | '@biomejs/cli-linux-x64@1.9.2': 195 | resolution: {integrity: sha512-T0cPk3C3Jr2pVlsuQVTBqk2qPjTm8cYcTD9p/wmR9MeVqui1C/xTVfOIwd3miRODFMrJaVQ8MYSXnVIhV9jTjg==} 196 | engines: {node: '>=14.21.3'} 197 | cpu: [x64] 198 | os: [linux] 199 | 200 | '@biomejs/cli-win32-arm64@1.9.2': 201 | resolution: {integrity: sha512-2x7gSty75bNIeD23ZRPXyox6Z/V0M71ObeJtvQBhi1fgrvPdtkEuw7/0wEHg6buNCubzOFuN9WYJm6FKoUHfhg==} 202 | engines: {node: '>=14.21.3'} 203 | cpu: [arm64] 204 | os: [win32] 205 | 206 | '@biomejs/cli-win32-x64@1.9.2': 207 | resolution: {integrity: sha512-JC3XvdYcjmu1FmAehVwVV0SebLpeNTnO2ZaMdGCSOdS7f8O9Fq14T2P1gTG1Q29Q8Dt1S03hh0IdVpIZykOL8g==} 208 | engines: {node: '>=14.21.3'} 209 | cpu: [x64] 210 | os: [win32] 211 | 212 | '@heroicons/react@2.1.5': 213 | resolution: {integrity: sha512-FuzFN+BsHa+7OxbvAERtgBTNeZpUjgM/MIizfVkSCL2/edriN0Hx/DWRCR//aPYwO5QX/YlgLGXk+E3PcfZwjA==} 214 | peerDependencies: 215 | react: '>= 16' 216 | 217 | '@isaacs/cliui@8.0.2': 218 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 219 | engines: {node: '>=12'} 220 | 221 | '@jridgewell/gen-mapping@0.3.5': 222 | resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 223 | engines: {node: '>=6.0.0'} 224 | 225 | '@jridgewell/resolve-uri@3.1.2': 226 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 227 | engines: {node: '>=6.0.0'} 228 | 229 | '@jridgewell/set-array@1.2.1': 230 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 231 | engines: {node: '>=6.0.0'} 232 | 233 | '@jridgewell/sourcemap-codec@1.5.0': 234 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 235 | 236 | '@jridgewell/trace-mapping@0.3.25': 237 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 238 | 239 | '@mapbox/node-pre-gyp@1.0.11': 240 | resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} 241 | hasBin: true 242 | 243 | '@neondatabase/serverless@0.9.5': 244 | resolution: {integrity: sha512-siFas6gItqv6wD/pZnvdu34wEqgG3nSE6zWZdq5j2DEsa+VvX8i/5HXJOo06qrw5axPXn+lGCxeR+NLaSPIXug==} 245 | 246 | '@next/env@14.2.13': 247 | resolution: {integrity: sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==} 248 | 249 | '@next/swc-darwin-arm64@14.2.13': 250 | resolution: {integrity: sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==} 251 | engines: {node: '>= 10'} 252 | cpu: [arm64] 253 | os: [darwin] 254 | 255 | '@next/swc-darwin-x64@14.2.13': 256 | resolution: {integrity: sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==} 257 | engines: {node: '>= 10'} 258 | cpu: [x64] 259 | os: [darwin] 260 | 261 | '@next/swc-linux-arm64-gnu@14.2.13': 262 | resolution: {integrity: sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==} 263 | engines: {node: '>= 10'} 264 | cpu: [arm64] 265 | os: [linux] 266 | 267 | '@next/swc-linux-arm64-musl@14.2.13': 268 | resolution: {integrity: sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==} 269 | engines: {node: '>= 10'} 270 | cpu: [arm64] 271 | os: [linux] 272 | 273 | '@next/swc-linux-x64-gnu@14.2.13': 274 | resolution: {integrity: sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==} 275 | engines: {node: '>= 10'} 276 | cpu: [x64] 277 | os: [linux] 278 | 279 | '@next/swc-linux-x64-musl@14.2.13': 280 | resolution: {integrity: sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==} 281 | engines: {node: '>= 10'} 282 | cpu: [x64] 283 | os: [linux] 284 | 285 | '@next/swc-win32-arm64-msvc@14.2.13': 286 | resolution: {integrity: sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==} 287 | engines: {node: '>= 10'} 288 | cpu: [arm64] 289 | os: [win32] 290 | 291 | '@next/swc-win32-ia32-msvc@14.2.13': 292 | resolution: {integrity: sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==} 293 | engines: {node: '>= 10'} 294 | cpu: [ia32] 295 | os: [win32] 296 | 297 | '@next/swc-win32-x64-msvc@14.2.13': 298 | resolution: {integrity: sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==} 299 | engines: {node: '>= 10'} 300 | cpu: [x64] 301 | os: [win32] 302 | 303 | '@nodelib/fs.scandir@2.1.5': 304 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 305 | engines: {node: '>= 8'} 306 | 307 | '@nodelib/fs.stat@2.0.5': 308 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 309 | engines: {node: '>= 8'} 310 | 311 | '@nodelib/fs.walk@1.2.8': 312 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 313 | engines: {node: '>= 8'} 314 | 315 | '@panva/hkdf@1.2.1': 316 | resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} 317 | 318 | '@pkgjs/parseargs@0.11.0': 319 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 320 | engines: {node: '>=14'} 321 | 322 | '@swc/counter@0.1.3': 323 | resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} 324 | 325 | '@swc/helpers@0.5.5': 326 | resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} 327 | 328 | '@tailwindcss/forms@0.5.9': 329 | resolution: {integrity: sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==} 330 | peerDependencies: 331 | tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20' 332 | 333 | '@trivago/prettier-plugin-sort-imports@4.3.0': 334 | resolution: {integrity: sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==} 335 | peerDependencies: 336 | '@vue/compiler-sfc': 3.x 337 | prettier: 2.x - 3.x 338 | peerDependenciesMeta: 339 | '@vue/compiler-sfc': 340 | optional: true 341 | 342 | '@types/bcrypt@5.0.2': 343 | resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} 344 | 345 | '@types/cookie@0.6.0': 346 | resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} 347 | 348 | '@types/node@22.10.1': 349 | resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} 350 | 351 | '@types/pg@8.11.6': 352 | resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} 353 | 354 | '@types/prop-types@15.7.13': 355 | resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} 356 | 357 | '@types/react-dom@18.3.0': 358 | resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} 359 | 360 | '@types/react@18.3.8': 361 | resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==} 362 | 363 | '@vercel/postgres@0.10.0': 364 | resolution: {integrity: sha512-fSD23DxGND40IzSkXjcFcxr53t3Tiym59Is0jSYIFpG4/0f0KO9SGtcp1sXiebvPaGe7N/tU05cH4yt2S6/IPg==} 365 | engines: {node: '>=18.14'} 366 | 367 | abbrev@1.1.1: 368 | resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} 369 | 370 | abort-controller@3.0.0: 371 | resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} 372 | engines: {node: '>=6.5'} 373 | 374 | agent-base@6.0.2: 375 | resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} 376 | engines: {node: '>= 6.0.0'} 377 | 378 | ansi-regex@5.0.1: 379 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 380 | engines: {node: '>=8'} 381 | 382 | ansi-regex@6.1.0: 383 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 384 | engines: {node: '>=12'} 385 | 386 | ansi-styles@4.3.0: 387 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 388 | engines: {node: '>=8'} 389 | 390 | ansi-styles@6.2.1: 391 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 392 | engines: {node: '>=12'} 393 | 394 | any-promise@1.3.0: 395 | resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 396 | 397 | anymatch@3.1.3: 398 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 399 | engines: {node: '>= 8'} 400 | 401 | aproba@2.0.0: 402 | resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} 403 | 404 | are-we-there-yet@2.0.0: 405 | resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} 406 | engines: {node: '>=10'} 407 | deprecated: This package is no longer supported. 408 | 409 | arg@5.0.2: 410 | resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} 411 | 412 | atomic-sleep@1.0.0: 413 | resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} 414 | engines: {node: '>=8.0.0'} 415 | 416 | autoprefixer@10.4.20: 417 | resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} 418 | engines: {node: ^10 || ^12 || >=14} 419 | hasBin: true 420 | peerDependencies: 421 | postcss: ^8.1.0 422 | 423 | balanced-match@1.0.2: 424 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 425 | 426 | base64-js@1.5.1: 427 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 428 | 429 | bcrypt@5.1.1: 430 | resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} 431 | engines: {node: '>= 10.0.0'} 432 | 433 | binary-extensions@2.3.0: 434 | resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 435 | engines: {node: '>=8'} 436 | 437 | brace-expansion@1.1.11: 438 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 439 | 440 | brace-expansion@2.0.1: 441 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 442 | 443 | braces@3.0.3: 444 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 445 | engines: {node: '>=8'} 446 | 447 | browserslist@4.24.2: 448 | resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} 449 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 450 | hasBin: true 451 | 452 | buffer@6.0.3: 453 | resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 454 | 455 | bufferutil@4.0.8: 456 | resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} 457 | engines: {node: '>=6.14.2'} 458 | 459 | busboy@1.6.0: 460 | resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} 461 | engines: {node: '>=10.16.0'} 462 | 463 | camelcase-css@2.0.1: 464 | resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} 465 | engines: {node: '>= 6'} 466 | 467 | caniuse-lite@1.0.30001680: 468 | resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} 469 | 470 | chokidar@3.6.0: 471 | resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 472 | engines: {node: '>= 8.10.0'} 473 | 474 | chownr@2.0.0: 475 | resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} 476 | engines: {node: '>=10'} 477 | 478 | client-only@0.0.1: 479 | resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} 480 | 481 | clsx@2.1.1: 482 | resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} 483 | engines: {node: '>=6'} 484 | 485 | color-convert@2.0.1: 486 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 487 | engines: {node: '>=7.0.0'} 488 | 489 | color-name@1.1.4: 490 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 491 | 492 | color-support@1.1.3: 493 | resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} 494 | hasBin: true 495 | 496 | commander@4.1.1: 497 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 498 | engines: {node: '>= 6'} 499 | 500 | concat-map@0.0.1: 501 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 502 | 503 | console-control-strings@1.1.0: 504 | resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} 505 | 506 | cookie@0.6.0: 507 | resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} 508 | engines: {node: '>= 0.6'} 509 | 510 | cross-spawn@7.0.5: 511 | resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} 512 | engines: {node: '>= 8'} 513 | 514 | cssesc@3.0.0: 515 | resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 516 | engines: {node: '>=4'} 517 | hasBin: true 518 | 519 | csstype@3.1.3: 520 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 521 | 522 | debug@4.3.7: 523 | resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} 524 | engines: {node: '>=6.0'} 525 | peerDependencies: 526 | supports-color: '*' 527 | peerDependenciesMeta: 528 | supports-color: 529 | optional: true 530 | 531 | delegates@1.0.0: 532 | resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} 533 | 534 | detect-libc@2.0.3: 535 | resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} 536 | engines: {node: '>=8'} 537 | 538 | didyoumean@1.2.2: 539 | resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} 540 | 541 | dlv@1.1.3: 542 | resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 543 | 544 | dotenv@16.4.5: 545 | resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} 546 | engines: {node: '>=12'} 547 | 548 | eastasianwidth@0.2.0: 549 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 550 | 551 | electron-to-chromium@1.5.55: 552 | resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} 553 | 554 | emoji-regex@8.0.0: 555 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 556 | 557 | emoji-regex@9.2.2: 558 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 559 | 560 | escalade@3.2.0: 561 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 562 | engines: {node: '>=6'} 563 | 564 | event-target-shim@5.0.1: 565 | resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} 566 | engines: {node: '>=6'} 567 | 568 | events@3.3.0: 569 | resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 570 | engines: {node: '>=0.8.x'} 571 | 572 | fast-glob@3.3.2: 573 | resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} 574 | engines: {node: '>=8.6.0'} 575 | 576 | fast-redact@3.5.0: 577 | resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} 578 | engines: {node: '>=6'} 579 | 580 | fastq@1.17.1: 581 | resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 582 | 583 | fill-range@7.1.1: 584 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 585 | engines: {node: '>=8'} 586 | 587 | foreground-child@3.3.0: 588 | resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} 589 | engines: {node: '>=14'} 590 | 591 | fraction.js@4.3.7: 592 | resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} 593 | 594 | fs-minipass@2.1.0: 595 | resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} 596 | engines: {node: '>= 8'} 597 | 598 | fs.realpath@1.0.0: 599 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 600 | 601 | fsevents@2.3.3: 602 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 603 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 604 | os: [darwin] 605 | 606 | function-bind@1.1.2: 607 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 608 | 609 | gauge@3.0.2: 610 | resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} 611 | engines: {node: '>=10'} 612 | deprecated: This package is no longer supported. 613 | 614 | glob-parent@5.1.2: 615 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 616 | engines: {node: '>= 6'} 617 | 618 | glob-parent@6.0.2: 619 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 620 | engines: {node: '>=10.13.0'} 621 | 622 | glob@10.4.5: 623 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 624 | hasBin: true 625 | 626 | glob@7.2.3: 627 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 628 | deprecated: Glob versions prior to v9 are no longer supported 629 | 630 | globals@11.12.0: 631 | resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 632 | engines: {node: '>=4'} 633 | 634 | graceful-fs@4.2.11: 635 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 636 | 637 | has-unicode@2.0.1: 638 | resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} 639 | 640 | hasown@2.0.2: 641 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 642 | engines: {node: '>= 0.4'} 643 | 644 | https-proxy-agent@5.0.1: 645 | resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} 646 | engines: {node: '>= 6'} 647 | 648 | ieee754@1.2.1: 649 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 650 | 651 | inflight@1.0.6: 652 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 653 | 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. 654 | 655 | inherits@2.0.4: 656 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 657 | 658 | is-binary-path@2.1.0: 659 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 660 | engines: {node: '>=8'} 661 | 662 | is-core-module@2.15.1: 663 | resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} 664 | engines: {node: '>= 0.4'} 665 | 666 | is-extglob@2.1.1: 667 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 668 | engines: {node: '>=0.10.0'} 669 | 670 | is-fullwidth-code-point@3.0.0: 671 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 672 | engines: {node: '>=8'} 673 | 674 | is-glob@4.0.3: 675 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 676 | engines: {node: '>=0.10.0'} 677 | 678 | is-number@7.0.0: 679 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 680 | engines: {node: '>=0.12.0'} 681 | 682 | isexe@2.0.0: 683 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 684 | 685 | jackspeak@3.4.3: 686 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 687 | 688 | javascript-natural-sort@0.7.1: 689 | resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} 690 | 691 | jiti@1.21.6: 692 | resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} 693 | hasBin: true 694 | 695 | jose@5.9.6: 696 | resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} 697 | 698 | js-tokens@4.0.0: 699 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 700 | 701 | jsesc@2.5.2: 702 | resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} 703 | engines: {node: '>=4'} 704 | hasBin: true 705 | 706 | jsesc@3.0.2: 707 | resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} 708 | engines: {node: '>=6'} 709 | hasBin: true 710 | 711 | lilconfig@2.1.0: 712 | resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} 713 | engines: {node: '>=10'} 714 | 715 | lilconfig@3.1.2: 716 | resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} 717 | engines: {node: '>=14'} 718 | 719 | lines-and-columns@1.2.4: 720 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 721 | 722 | lodash@4.17.21: 723 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 724 | 725 | loose-envify@1.4.0: 726 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 727 | hasBin: true 728 | 729 | lru-cache@10.4.3: 730 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 731 | 732 | make-dir@3.1.0: 733 | resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} 734 | engines: {node: '>=8'} 735 | 736 | merge2@1.4.1: 737 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 738 | engines: {node: '>= 8'} 739 | 740 | micromatch@4.0.8: 741 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 742 | engines: {node: '>=8.6'} 743 | 744 | mini-svg-data-uri@1.4.4: 745 | resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} 746 | hasBin: true 747 | 748 | minimatch@3.1.2: 749 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 750 | 751 | minimatch@9.0.5: 752 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 753 | engines: {node: '>=16 || 14 >=14.17'} 754 | 755 | minipass@3.3.6: 756 | resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} 757 | engines: {node: '>=8'} 758 | 759 | minipass@5.0.0: 760 | resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} 761 | engines: {node: '>=8'} 762 | 763 | minipass@7.1.2: 764 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 765 | engines: {node: '>=16 || 14 >=14.17'} 766 | 767 | minizlib@2.1.2: 768 | resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} 769 | engines: {node: '>= 8'} 770 | 771 | mkdirp@1.0.4: 772 | resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} 773 | engines: {node: '>=10'} 774 | hasBin: true 775 | 776 | ms@2.1.3: 777 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 778 | 779 | mz@2.7.0: 780 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 781 | 782 | nanoid@3.3.7: 783 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 784 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 785 | hasBin: true 786 | 787 | next-auth@5.0.0-beta.21: 788 | resolution: {integrity: sha512-VrT6LV9u/o5tMuWxRDN5d/K1OgPskrMHy+aITTwasVfIrkIaU/8UVg3TXH8ynJgzhfzlDx/3hLWGhi+eXQw4qg==} 789 | peerDependencies: 790 | '@simplewebauthn/browser': ^9.0.1 791 | '@simplewebauthn/server': ^9.0.2 792 | next: ^14.0.0-0 || ^15.0.0-0 793 | nodemailer: ^6.6.5 794 | react: ^18.2.0 || ^19.0.0-0 795 | peerDependenciesMeta: 796 | '@simplewebauthn/browser': 797 | optional: true 798 | '@simplewebauthn/server': 799 | optional: true 800 | nodemailer: 801 | optional: true 802 | 803 | next@14.2.13: 804 | resolution: {integrity: sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==} 805 | engines: {node: '>=18.17.0'} 806 | hasBin: true 807 | peerDependencies: 808 | '@opentelemetry/api': ^1.1.0 809 | '@playwright/test': ^1.41.2 810 | react: ^18.2.0 811 | react-dom: ^18.2.0 812 | sass: ^1.3.0 813 | peerDependenciesMeta: 814 | '@opentelemetry/api': 815 | optional: true 816 | '@playwright/test': 817 | optional: true 818 | sass: 819 | optional: true 820 | 821 | node-addon-api@5.1.0: 822 | resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} 823 | 824 | node-fetch@2.7.0: 825 | resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} 826 | engines: {node: 4.x || >=6.0.0} 827 | peerDependencies: 828 | encoding: ^0.1.0 829 | peerDependenciesMeta: 830 | encoding: 831 | optional: true 832 | 833 | node-gyp-build@4.8.2: 834 | resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} 835 | hasBin: true 836 | 837 | node-releases@2.0.18: 838 | resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} 839 | 840 | nopt@5.0.0: 841 | resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} 842 | engines: {node: '>=6'} 843 | hasBin: true 844 | 845 | normalize-path@3.0.0: 846 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 847 | engines: {node: '>=0.10.0'} 848 | 849 | normalize-range@0.1.2: 850 | resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} 851 | engines: {node: '>=0.10.0'} 852 | 853 | npmlog@5.0.1: 854 | resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} 855 | deprecated: This package is no longer supported. 856 | 857 | oauth4webapi@2.17.0: 858 | resolution: {integrity: sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==} 859 | 860 | object-assign@4.1.1: 861 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 862 | engines: {node: '>=0.10.0'} 863 | 864 | object-hash@3.0.0: 865 | resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} 866 | engines: {node: '>= 6'} 867 | 868 | obuf@1.1.2: 869 | resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} 870 | 871 | on-exit-leak-free@2.1.2: 872 | resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} 873 | engines: {node: '>=14.0.0'} 874 | 875 | once@1.4.0: 876 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 877 | 878 | package-json-from-dist@1.0.1: 879 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 880 | 881 | path-is-absolute@1.0.1: 882 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 883 | engines: {node: '>=0.10.0'} 884 | 885 | path-key@3.1.1: 886 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 887 | engines: {node: '>=8'} 888 | 889 | path-parse@1.0.7: 890 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 891 | 892 | path-scurry@1.11.1: 893 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 894 | engines: {node: '>=16 || 14 >=14.18'} 895 | 896 | pg-int8@1.0.1: 897 | resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} 898 | engines: {node: '>=4.0.0'} 899 | 900 | pg-numeric@1.0.2: 901 | resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} 902 | engines: {node: '>=4'} 903 | 904 | pg-protocol@1.7.0: 905 | resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} 906 | 907 | pg-types@4.0.2: 908 | resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} 909 | engines: {node: '>=10'} 910 | 911 | picocolors@1.1.1: 912 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 913 | 914 | picomatch@2.3.1: 915 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 916 | engines: {node: '>=8.6'} 917 | 918 | pify@2.3.0: 919 | resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 920 | engines: {node: '>=0.10.0'} 921 | 922 | pino-abstract-transport@1.2.0: 923 | resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} 924 | 925 | pino-std-serializers@7.0.0: 926 | resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} 927 | 928 | pino@9.4.0: 929 | resolution: {integrity: sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==} 930 | hasBin: true 931 | 932 | pirates@4.0.6: 933 | resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} 934 | engines: {node: '>= 6'} 935 | 936 | postcss-import@15.1.0: 937 | resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} 938 | engines: {node: '>=14.0.0'} 939 | peerDependencies: 940 | postcss: ^8.0.0 941 | 942 | postcss-js@4.0.1: 943 | resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} 944 | engines: {node: ^12 || ^14 || >= 16} 945 | peerDependencies: 946 | postcss: ^8.4.21 947 | 948 | postcss-load-config@4.0.2: 949 | resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} 950 | engines: {node: '>= 14'} 951 | peerDependencies: 952 | postcss: '>=8.0.9' 953 | ts-node: '>=9.0.0' 954 | peerDependenciesMeta: 955 | postcss: 956 | optional: true 957 | ts-node: 958 | optional: true 959 | 960 | postcss-nested@6.2.0: 961 | resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} 962 | engines: {node: '>=12.0'} 963 | peerDependencies: 964 | postcss: ^8.2.14 965 | 966 | postcss-selector-parser@6.1.2: 967 | resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} 968 | engines: {node: '>=4'} 969 | 970 | postcss-value-parser@4.2.0: 971 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 972 | 973 | postcss@8.4.31: 974 | resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} 975 | engines: {node: ^10 || ^12 || >=14} 976 | 977 | postcss@8.4.47: 978 | resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} 979 | engines: {node: ^10 || ^12 || >=14} 980 | 981 | postgres-array@3.0.2: 982 | resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} 983 | engines: {node: '>=12'} 984 | 985 | postgres-bytea@3.0.0: 986 | resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} 987 | engines: {node: '>= 6'} 988 | 989 | postgres-date@2.1.0: 990 | resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} 991 | engines: {node: '>=12'} 992 | 993 | postgres-interval@3.0.0: 994 | resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} 995 | engines: {node: '>=12'} 996 | 997 | postgres-range@1.1.4: 998 | resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} 999 | 1000 | preact-render-to-string@5.2.3: 1001 | resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} 1002 | peerDependencies: 1003 | preact: '>=10' 1004 | 1005 | preact@10.11.3: 1006 | resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} 1007 | 1008 | prettier@3.3.3: 1009 | resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} 1010 | engines: {node: '>=14'} 1011 | hasBin: true 1012 | 1013 | pretty-format@3.8.0: 1014 | resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} 1015 | 1016 | process-warning@4.0.0: 1017 | resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} 1018 | 1019 | process@0.11.10: 1020 | resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} 1021 | engines: {node: '>= 0.6.0'} 1022 | 1023 | queue-microtask@1.2.3: 1024 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1025 | 1026 | quick-format-unescaped@4.0.4: 1027 | resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} 1028 | 1029 | react-dom@18.3.1: 1030 | resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} 1031 | peerDependencies: 1032 | react: ^18.3.1 1033 | 1034 | react@18.3.1: 1035 | resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} 1036 | engines: {node: '>=0.10.0'} 1037 | 1038 | read-cache@1.0.0: 1039 | resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 1040 | 1041 | readable-stream@3.6.2: 1042 | resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1043 | engines: {node: '>= 6'} 1044 | 1045 | readable-stream@4.5.2: 1046 | resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} 1047 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1048 | 1049 | readdirp@3.6.0: 1050 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1051 | engines: {node: '>=8.10.0'} 1052 | 1053 | real-require@0.2.0: 1054 | resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} 1055 | engines: {node: '>= 12.13.0'} 1056 | 1057 | resolve@1.22.8: 1058 | resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 1059 | hasBin: true 1060 | 1061 | reusify@1.0.4: 1062 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1063 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1064 | 1065 | rimraf@3.0.2: 1066 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1067 | deprecated: Rimraf versions prior to v4 are no longer supported 1068 | hasBin: true 1069 | 1070 | run-parallel@1.2.0: 1071 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1072 | 1073 | safe-buffer@5.2.1: 1074 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1075 | 1076 | safe-stable-stringify@2.5.0: 1077 | resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} 1078 | engines: {node: '>=10'} 1079 | 1080 | scheduler@0.23.2: 1081 | resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} 1082 | 1083 | semver@6.3.1: 1084 | resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1085 | hasBin: true 1086 | 1087 | semver@7.6.3: 1088 | resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} 1089 | engines: {node: '>=10'} 1090 | hasBin: true 1091 | 1092 | set-blocking@2.0.0: 1093 | resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} 1094 | 1095 | shebang-command@2.0.0: 1096 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1097 | engines: {node: '>=8'} 1098 | 1099 | shebang-regex@3.0.0: 1100 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1101 | engines: {node: '>=8'} 1102 | 1103 | signal-exit@3.0.7: 1104 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 1105 | 1106 | signal-exit@4.1.0: 1107 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1108 | engines: {node: '>=14'} 1109 | 1110 | sonic-boom@4.2.0: 1111 | resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} 1112 | 1113 | source-map-js@1.2.1: 1114 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1115 | engines: {node: '>=0.10.0'} 1116 | 1117 | source-map@0.5.7: 1118 | resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} 1119 | engines: {node: '>=0.10.0'} 1120 | 1121 | split2@4.2.0: 1122 | resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} 1123 | engines: {node: '>= 10.x'} 1124 | 1125 | streamsearch@1.1.0: 1126 | resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} 1127 | engines: {node: '>=10.0.0'} 1128 | 1129 | string-width@4.2.3: 1130 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1131 | engines: {node: '>=8'} 1132 | 1133 | string-width@5.1.2: 1134 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 1135 | engines: {node: '>=12'} 1136 | 1137 | string_decoder@1.3.0: 1138 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1139 | 1140 | strip-ansi@6.0.1: 1141 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1142 | engines: {node: '>=8'} 1143 | 1144 | strip-ansi@7.1.0: 1145 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 1146 | engines: {node: '>=12'} 1147 | 1148 | styled-jsx@5.1.1: 1149 | resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} 1150 | engines: {node: '>= 12.0.0'} 1151 | peerDependencies: 1152 | '@babel/core': '*' 1153 | babel-plugin-macros: '*' 1154 | react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' 1155 | peerDependenciesMeta: 1156 | '@babel/core': 1157 | optional: true 1158 | babel-plugin-macros: 1159 | optional: true 1160 | 1161 | sucrase@3.35.0: 1162 | resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} 1163 | engines: {node: '>=16 || 14 >=14.17'} 1164 | hasBin: true 1165 | 1166 | supports-preserve-symlinks-flag@1.0.0: 1167 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1168 | engines: {node: '>= 0.4'} 1169 | 1170 | tailwindcss@3.4.13: 1171 | resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} 1172 | engines: {node: '>=14.0.0'} 1173 | hasBin: true 1174 | 1175 | tar@6.2.1: 1176 | resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} 1177 | engines: {node: '>=10'} 1178 | 1179 | thenify-all@1.6.0: 1180 | resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 1181 | engines: {node: '>=0.8'} 1182 | 1183 | thenify@3.3.1: 1184 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 1185 | 1186 | thread-stream@3.1.0: 1187 | resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} 1188 | 1189 | to-fast-properties@2.0.0: 1190 | resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} 1191 | engines: {node: '>=4'} 1192 | 1193 | to-regex-range@5.0.1: 1194 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1195 | engines: {node: '>=8.0'} 1196 | 1197 | tr46@0.0.3: 1198 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} 1199 | 1200 | ts-interface-checker@0.1.13: 1201 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 1202 | 1203 | tslib@2.8.1: 1204 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1205 | 1206 | typescript@5.6.2: 1207 | resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} 1208 | engines: {node: '>=14.17'} 1209 | hasBin: true 1210 | 1211 | undici-types@6.20.0: 1212 | resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 1213 | 1214 | update-browserslist-db@1.1.1: 1215 | resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} 1216 | hasBin: true 1217 | peerDependencies: 1218 | browserslist: '>= 4.21.0' 1219 | 1220 | use-debounce@10.0.3: 1221 | resolution: {integrity: sha512-DxQSI9ZKso689WM1mjgGU3ozcxU1TJElBJ3X6S4SMzMNcm2lVH0AHmyXB+K7ewjz2BSUKJTDqTcwtSMRfB89dg==} 1222 | engines: {node: '>= 16.0.0'} 1223 | peerDependencies: 1224 | react: '*' 1225 | 1226 | util-deprecate@1.0.2: 1227 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1228 | 1229 | webidl-conversions@3.0.1: 1230 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} 1231 | 1232 | whatwg-url@5.0.0: 1233 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} 1234 | 1235 | which@2.0.2: 1236 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1237 | engines: {node: '>= 8'} 1238 | hasBin: true 1239 | 1240 | wide-align@1.1.5: 1241 | resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} 1242 | 1243 | wrap-ansi@7.0.0: 1244 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1245 | engines: {node: '>=10'} 1246 | 1247 | wrap-ansi@8.1.0: 1248 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 1249 | engines: {node: '>=12'} 1250 | 1251 | wrappy@1.0.2: 1252 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1253 | 1254 | ws@8.18.0: 1255 | resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} 1256 | engines: {node: '>=10.0.0'} 1257 | peerDependencies: 1258 | bufferutil: ^4.0.1 1259 | utf-8-validate: '>=5.0.2' 1260 | peerDependenciesMeta: 1261 | bufferutil: 1262 | optional: true 1263 | utf-8-validate: 1264 | optional: true 1265 | 1266 | yallist@4.0.0: 1267 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1268 | 1269 | yaml@2.6.0: 1270 | resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} 1271 | engines: {node: '>= 14'} 1272 | hasBin: true 1273 | 1274 | zod@3.23.8: 1275 | resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} 1276 | 1277 | snapshots: 1278 | 1279 | '@alloc/quick-lru@5.2.0': {} 1280 | 1281 | '@auth/core@0.35.0': 1282 | dependencies: 1283 | '@panva/hkdf': 1.2.1 1284 | '@types/cookie': 0.6.0 1285 | cookie: 0.6.0 1286 | jose: 5.9.6 1287 | oauth4webapi: 2.17.0 1288 | preact: 10.11.3 1289 | preact-render-to-string: 5.2.3(preact@10.11.3) 1290 | 1291 | '@babel/code-frame@7.26.2': 1292 | dependencies: 1293 | '@babel/helper-validator-identifier': 7.25.9 1294 | js-tokens: 4.0.0 1295 | picocolors: 1.1.1 1296 | 1297 | '@babel/generator@7.17.7': 1298 | dependencies: 1299 | '@babel/types': 7.17.0 1300 | jsesc: 2.5.2 1301 | source-map: 0.5.7 1302 | 1303 | '@babel/generator@7.26.2': 1304 | dependencies: 1305 | '@babel/parser': 7.26.2 1306 | '@babel/types': 7.26.0 1307 | '@jridgewell/gen-mapping': 0.3.5 1308 | '@jridgewell/trace-mapping': 0.3.25 1309 | jsesc: 3.0.2 1310 | 1311 | '@babel/helper-environment-visitor@7.24.7': 1312 | dependencies: 1313 | '@babel/types': 7.26.0 1314 | 1315 | '@babel/helper-function-name@7.24.7': 1316 | dependencies: 1317 | '@babel/template': 7.25.9 1318 | '@babel/types': 7.26.0 1319 | 1320 | '@babel/helper-hoist-variables@7.24.7': 1321 | dependencies: 1322 | '@babel/types': 7.26.0 1323 | 1324 | '@babel/helper-split-export-declaration@7.24.7': 1325 | dependencies: 1326 | '@babel/types': 7.26.0 1327 | 1328 | '@babel/helper-string-parser@7.25.9': {} 1329 | 1330 | '@babel/helper-validator-identifier@7.25.9': {} 1331 | 1332 | '@babel/parser@7.26.2': 1333 | dependencies: 1334 | '@babel/types': 7.26.0 1335 | 1336 | '@babel/template@7.25.9': 1337 | dependencies: 1338 | '@babel/code-frame': 7.26.2 1339 | '@babel/parser': 7.26.2 1340 | '@babel/types': 7.26.0 1341 | 1342 | '@babel/traverse@7.23.2': 1343 | dependencies: 1344 | '@babel/code-frame': 7.26.2 1345 | '@babel/generator': 7.26.2 1346 | '@babel/helper-environment-visitor': 7.24.7 1347 | '@babel/helper-function-name': 7.24.7 1348 | '@babel/helper-hoist-variables': 7.24.7 1349 | '@babel/helper-split-export-declaration': 7.24.7 1350 | '@babel/parser': 7.26.2 1351 | '@babel/types': 7.26.0 1352 | debug: 4.3.7 1353 | globals: 11.12.0 1354 | transitivePeerDependencies: 1355 | - supports-color 1356 | 1357 | '@babel/types@7.17.0': 1358 | dependencies: 1359 | '@babel/helper-validator-identifier': 7.25.9 1360 | to-fast-properties: 2.0.0 1361 | 1362 | '@babel/types@7.26.0': 1363 | dependencies: 1364 | '@babel/helper-string-parser': 7.25.9 1365 | '@babel/helper-validator-identifier': 7.25.9 1366 | 1367 | '@biomejs/biome@1.9.2': 1368 | optionalDependencies: 1369 | '@biomejs/cli-darwin-arm64': 1.9.2 1370 | '@biomejs/cli-darwin-x64': 1.9.2 1371 | '@biomejs/cli-linux-arm64': 1.9.2 1372 | '@biomejs/cli-linux-arm64-musl': 1.9.2 1373 | '@biomejs/cli-linux-x64': 1.9.2 1374 | '@biomejs/cli-linux-x64-musl': 1.9.2 1375 | '@biomejs/cli-win32-arm64': 1.9.2 1376 | '@biomejs/cli-win32-x64': 1.9.2 1377 | 1378 | '@biomejs/cli-darwin-arm64@1.9.2': 1379 | optional: true 1380 | 1381 | '@biomejs/cli-darwin-x64@1.9.2': 1382 | optional: true 1383 | 1384 | '@biomejs/cli-linux-arm64-musl@1.9.2': 1385 | optional: true 1386 | 1387 | '@biomejs/cli-linux-arm64@1.9.2': 1388 | optional: true 1389 | 1390 | '@biomejs/cli-linux-x64-musl@1.9.2': 1391 | optional: true 1392 | 1393 | '@biomejs/cli-linux-x64@1.9.2': 1394 | optional: true 1395 | 1396 | '@biomejs/cli-win32-arm64@1.9.2': 1397 | optional: true 1398 | 1399 | '@biomejs/cli-win32-x64@1.9.2': 1400 | optional: true 1401 | 1402 | '@heroicons/react@2.1.5(react@18.3.1)': 1403 | dependencies: 1404 | react: 18.3.1 1405 | 1406 | '@isaacs/cliui@8.0.2': 1407 | dependencies: 1408 | string-width: 5.1.2 1409 | string-width-cjs: string-width@4.2.3 1410 | strip-ansi: 7.1.0 1411 | strip-ansi-cjs: strip-ansi@6.0.1 1412 | wrap-ansi: 8.1.0 1413 | wrap-ansi-cjs: wrap-ansi@7.0.0 1414 | 1415 | '@jridgewell/gen-mapping@0.3.5': 1416 | dependencies: 1417 | '@jridgewell/set-array': 1.2.1 1418 | '@jridgewell/sourcemap-codec': 1.5.0 1419 | '@jridgewell/trace-mapping': 0.3.25 1420 | 1421 | '@jridgewell/resolve-uri@3.1.2': {} 1422 | 1423 | '@jridgewell/set-array@1.2.1': {} 1424 | 1425 | '@jridgewell/sourcemap-codec@1.5.0': {} 1426 | 1427 | '@jridgewell/trace-mapping@0.3.25': 1428 | dependencies: 1429 | '@jridgewell/resolve-uri': 3.1.2 1430 | '@jridgewell/sourcemap-codec': 1.5.0 1431 | 1432 | '@mapbox/node-pre-gyp@1.0.11': 1433 | dependencies: 1434 | detect-libc: 2.0.3 1435 | https-proxy-agent: 5.0.1 1436 | make-dir: 3.1.0 1437 | node-fetch: 2.7.0 1438 | nopt: 5.0.0 1439 | npmlog: 5.0.1 1440 | rimraf: 3.0.2 1441 | semver: 7.6.3 1442 | tar: 6.2.1 1443 | transitivePeerDependencies: 1444 | - encoding 1445 | - supports-color 1446 | 1447 | '@neondatabase/serverless@0.9.5': 1448 | dependencies: 1449 | '@types/pg': 8.11.6 1450 | 1451 | '@next/env@14.2.13': {} 1452 | 1453 | '@next/swc-darwin-arm64@14.2.13': 1454 | optional: true 1455 | 1456 | '@next/swc-darwin-x64@14.2.13': 1457 | optional: true 1458 | 1459 | '@next/swc-linux-arm64-gnu@14.2.13': 1460 | optional: true 1461 | 1462 | '@next/swc-linux-arm64-musl@14.2.13': 1463 | optional: true 1464 | 1465 | '@next/swc-linux-x64-gnu@14.2.13': 1466 | optional: true 1467 | 1468 | '@next/swc-linux-x64-musl@14.2.13': 1469 | optional: true 1470 | 1471 | '@next/swc-win32-arm64-msvc@14.2.13': 1472 | optional: true 1473 | 1474 | '@next/swc-win32-ia32-msvc@14.2.13': 1475 | optional: true 1476 | 1477 | '@next/swc-win32-x64-msvc@14.2.13': 1478 | optional: true 1479 | 1480 | '@nodelib/fs.scandir@2.1.5': 1481 | dependencies: 1482 | '@nodelib/fs.stat': 2.0.5 1483 | run-parallel: 1.2.0 1484 | 1485 | '@nodelib/fs.stat@2.0.5': {} 1486 | 1487 | '@nodelib/fs.walk@1.2.8': 1488 | dependencies: 1489 | '@nodelib/fs.scandir': 2.1.5 1490 | fastq: 1.17.1 1491 | 1492 | '@panva/hkdf@1.2.1': {} 1493 | 1494 | '@pkgjs/parseargs@0.11.0': 1495 | optional: true 1496 | 1497 | '@swc/counter@0.1.3': {} 1498 | 1499 | '@swc/helpers@0.5.5': 1500 | dependencies: 1501 | '@swc/counter': 0.1.3 1502 | tslib: 2.8.1 1503 | 1504 | '@tailwindcss/forms@0.5.9(tailwindcss@3.4.13)': 1505 | dependencies: 1506 | mini-svg-data-uri: 1.4.4 1507 | tailwindcss: 3.4.13 1508 | 1509 | '@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.3.3)': 1510 | dependencies: 1511 | '@babel/generator': 7.17.7 1512 | '@babel/parser': 7.26.2 1513 | '@babel/traverse': 7.23.2 1514 | '@babel/types': 7.17.0 1515 | javascript-natural-sort: 0.7.1 1516 | lodash: 4.17.21 1517 | prettier: 3.3.3 1518 | transitivePeerDependencies: 1519 | - supports-color 1520 | 1521 | '@types/bcrypt@5.0.2': 1522 | dependencies: 1523 | '@types/node': 22.10.1 1524 | 1525 | '@types/cookie@0.6.0': {} 1526 | 1527 | '@types/node@22.10.1': 1528 | dependencies: 1529 | undici-types: 6.20.0 1530 | 1531 | '@types/pg@8.11.6': 1532 | dependencies: 1533 | '@types/node': 22.10.1 1534 | pg-protocol: 1.7.0 1535 | pg-types: 4.0.2 1536 | 1537 | '@types/prop-types@15.7.13': {} 1538 | 1539 | '@types/react-dom@18.3.0': 1540 | dependencies: 1541 | '@types/react': 18.3.8 1542 | 1543 | '@types/react@18.3.8': 1544 | dependencies: 1545 | '@types/prop-types': 15.7.13 1546 | csstype: 3.1.3 1547 | 1548 | '@vercel/postgres@0.10.0': 1549 | dependencies: 1550 | '@neondatabase/serverless': 0.9.5 1551 | bufferutil: 4.0.8 1552 | ws: 8.18.0(bufferutil@4.0.8) 1553 | transitivePeerDependencies: 1554 | - utf-8-validate 1555 | 1556 | abbrev@1.1.1: {} 1557 | 1558 | abort-controller@3.0.0: 1559 | dependencies: 1560 | event-target-shim: 5.0.1 1561 | 1562 | agent-base@6.0.2: 1563 | dependencies: 1564 | debug: 4.3.7 1565 | transitivePeerDependencies: 1566 | - supports-color 1567 | 1568 | ansi-regex@5.0.1: {} 1569 | 1570 | ansi-regex@6.1.0: {} 1571 | 1572 | ansi-styles@4.3.0: 1573 | dependencies: 1574 | color-convert: 2.0.1 1575 | 1576 | ansi-styles@6.2.1: {} 1577 | 1578 | any-promise@1.3.0: {} 1579 | 1580 | anymatch@3.1.3: 1581 | dependencies: 1582 | normalize-path: 3.0.0 1583 | picomatch: 2.3.1 1584 | 1585 | aproba@2.0.0: {} 1586 | 1587 | are-we-there-yet@2.0.0: 1588 | dependencies: 1589 | delegates: 1.0.0 1590 | readable-stream: 3.6.2 1591 | 1592 | arg@5.0.2: {} 1593 | 1594 | atomic-sleep@1.0.0: {} 1595 | 1596 | autoprefixer@10.4.20(postcss@8.4.47): 1597 | dependencies: 1598 | browserslist: 4.24.2 1599 | caniuse-lite: 1.0.30001680 1600 | fraction.js: 4.3.7 1601 | normalize-range: 0.1.2 1602 | picocolors: 1.1.1 1603 | postcss: 8.4.47 1604 | postcss-value-parser: 4.2.0 1605 | 1606 | balanced-match@1.0.2: {} 1607 | 1608 | base64-js@1.5.1: {} 1609 | 1610 | bcrypt@5.1.1: 1611 | dependencies: 1612 | '@mapbox/node-pre-gyp': 1.0.11 1613 | node-addon-api: 5.1.0 1614 | transitivePeerDependencies: 1615 | - encoding 1616 | - supports-color 1617 | 1618 | binary-extensions@2.3.0: {} 1619 | 1620 | brace-expansion@1.1.11: 1621 | dependencies: 1622 | balanced-match: 1.0.2 1623 | concat-map: 0.0.1 1624 | 1625 | brace-expansion@2.0.1: 1626 | dependencies: 1627 | balanced-match: 1.0.2 1628 | 1629 | braces@3.0.3: 1630 | dependencies: 1631 | fill-range: 7.1.1 1632 | 1633 | browserslist@4.24.2: 1634 | dependencies: 1635 | caniuse-lite: 1.0.30001680 1636 | electron-to-chromium: 1.5.55 1637 | node-releases: 2.0.18 1638 | update-browserslist-db: 1.1.1(browserslist@4.24.2) 1639 | 1640 | buffer@6.0.3: 1641 | dependencies: 1642 | base64-js: 1.5.1 1643 | ieee754: 1.2.1 1644 | 1645 | bufferutil@4.0.8: 1646 | dependencies: 1647 | node-gyp-build: 4.8.2 1648 | 1649 | busboy@1.6.0: 1650 | dependencies: 1651 | streamsearch: 1.1.0 1652 | 1653 | camelcase-css@2.0.1: {} 1654 | 1655 | caniuse-lite@1.0.30001680: {} 1656 | 1657 | chokidar@3.6.0: 1658 | dependencies: 1659 | anymatch: 3.1.3 1660 | braces: 3.0.3 1661 | glob-parent: 5.1.2 1662 | is-binary-path: 2.1.0 1663 | is-glob: 4.0.3 1664 | normalize-path: 3.0.0 1665 | readdirp: 3.6.0 1666 | optionalDependencies: 1667 | fsevents: 2.3.3 1668 | 1669 | chownr@2.0.0: {} 1670 | 1671 | client-only@0.0.1: {} 1672 | 1673 | clsx@2.1.1: {} 1674 | 1675 | color-convert@2.0.1: 1676 | dependencies: 1677 | color-name: 1.1.4 1678 | 1679 | color-name@1.1.4: {} 1680 | 1681 | color-support@1.1.3: {} 1682 | 1683 | commander@4.1.1: {} 1684 | 1685 | concat-map@0.0.1: {} 1686 | 1687 | console-control-strings@1.1.0: {} 1688 | 1689 | cookie@0.6.0: {} 1690 | 1691 | cross-spawn@7.0.5: 1692 | dependencies: 1693 | path-key: 3.1.1 1694 | shebang-command: 2.0.0 1695 | which: 2.0.2 1696 | 1697 | cssesc@3.0.0: {} 1698 | 1699 | csstype@3.1.3: {} 1700 | 1701 | debug@4.3.7: 1702 | dependencies: 1703 | ms: 2.1.3 1704 | 1705 | delegates@1.0.0: {} 1706 | 1707 | detect-libc@2.0.3: {} 1708 | 1709 | didyoumean@1.2.2: {} 1710 | 1711 | dlv@1.1.3: {} 1712 | 1713 | dotenv@16.4.5: {} 1714 | 1715 | eastasianwidth@0.2.0: {} 1716 | 1717 | electron-to-chromium@1.5.55: {} 1718 | 1719 | emoji-regex@8.0.0: {} 1720 | 1721 | emoji-regex@9.2.2: {} 1722 | 1723 | escalade@3.2.0: {} 1724 | 1725 | event-target-shim@5.0.1: {} 1726 | 1727 | events@3.3.0: {} 1728 | 1729 | fast-glob@3.3.2: 1730 | dependencies: 1731 | '@nodelib/fs.stat': 2.0.5 1732 | '@nodelib/fs.walk': 1.2.8 1733 | glob-parent: 5.1.2 1734 | merge2: 1.4.1 1735 | micromatch: 4.0.8 1736 | 1737 | fast-redact@3.5.0: {} 1738 | 1739 | fastq@1.17.1: 1740 | dependencies: 1741 | reusify: 1.0.4 1742 | 1743 | fill-range@7.1.1: 1744 | dependencies: 1745 | to-regex-range: 5.0.1 1746 | 1747 | foreground-child@3.3.0: 1748 | dependencies: 1749 | cross-spawn: 7.0.5 1750 | signal-exit: 4.1.0 1751 | 1752 | fraction.js@4.3.7: {} 1753 | 1754 | fs-minipass@2.1.0: 1755 | dependencies: 1756 | minipass: 3.3.6 1757 | 1758 | fs.realpath@1.0.0: {} 1759 | 1760 | fsevents@2.3.3: 1761 | optional: true 1762 | 1763 | function-bind@1.1.2: {} 1764 | 1765 | gauge@3.0.2: 1766 | dependencies: 1767 | aproba: 2.0.0 1768 | color-support: 1.1.3 1769 | console-control-strings: 1.1.0 1770 | has-unicode: 2.0.1 1771 | object-assign: 4.1.1 1772 | signal-exit: 3.0.7 1773 | string-width: 4.2.3 1774 | strip-ansi: 6.0.1 1775 | wide-align: 1.1.5 1776 | 1777 | glob-parent@5.1.2: 1778 | dependencies: 1779 | is-glob: 4.0.3 1780 | 1781 | glob-parent@6.0.2: 1782 | dependencies: 1783 | is-glob: 4.0.3 1784 | 1785 | glob@10.4.5: 1786 | dependencies: 1787 | foreground-child: 3.3.0 1788 | jackspeak: 3.4.3 1789 | minimatch: 9.0.5 1790 | minipass: 7.1.2 1791 | package-json-from-dist: 1.0.1 1792 | path-scurry: 1.11.1 1793 | 1794 | glob@7.2.3: 1795 | dependencies: 1796 | fs.realpath: 1.0.0 1797 | inflight: 1.0.6 1798 | inherits: 2.0.4 1799 | minimatch: 3.1.2 1800 | once: 1.4.0 1801 | path-is-absolute: 1.0.1 1802 | 1803 | globals@11.12.0: {} 1804 | 1805 | graceful-fs@4.2.11: {} 1806 | 1807 | has-unicode@2.0.1: {} 1808 | 1809 | hasown@2.0.2: 1810 | dependencies: 1811 | function-bind: 1.1.2 1812 | 1813 | https-proxy-agent@5.0.1: 1814 | dependencies: 1815 | agent-base: 6.0.2 1816 | debug: 4.3.7 1817 | transitivePeerDependencies: 1818 | - supports-color 1819 | 1820 | ieee754@1.2.1: {} 1821 | 1822 | inflight@1.0.6: 1823 | dependencies: 1824 | once: 1.4.0 1825 | wrappy: 1.0.2 1826 | 1827 | inherits@2.0.4: {} 1828 | 1829 | is-binary-path@2.1.0: 1830 | dependencies: 1831 | binary-extensions: 2.3.0 1832 | 1833 | is-core-module@2.15.1: 1834 | dependencies: 1835 | hasown: 2.0.2 1836 | 1837 | is-extglob@2.1.1: {} 1838 | 1839 | is-fullwidth-code-point@3.0.0: {} 1840 | 1841 | is-glob@4.0.3: 1842 | dependencies: 1843 | is-extglob: 2.1.1 1844 | 1845 | is-number@7.0.0: {} 1846 | 1847 | isexe@2.0.0: {} 1848 | 1849 | jackspeak@3.4.3: 1850 | dependencies: 1851 | '@isaacs/cliui': 8.0.2 1852 | optionalDependencies: 1853 | '@pkgjs/parseargs': 0.11.0 1854 | 1855 | javascript-natural-sort@0.7.1: {} 1856 | 1857 | jiti@1.21.6: {} 1858 | 1859 | jose@5.9.6: {} 1860 | 1861 | js-tokens@4.0.0: {} 1862 | 1863 | jsesc@2.5.2: {} 1864 | 1865 | jsesc@3.0.2: {} 1866 | 1867 | lilconfig@2.1.0: {} 1868 | 1869 | lilconfig@3.1.2: {} 1870 | 1871 | lines-and-columns@1.2.4: {} 1872 | 1873 | lodash@4.17.21: {} 1874 | 1875 | loose-envify@1.4.0: 1876 | dependencies: 1877 | js-tokens: 4.0.0 1878 | 1879 | lru-cache@10.4.3: {} 1880 | 1881 | make-dir@3.1.0: 1882 | dependencies: 1883 | semver: 6.3.1 1884 | 1885 | merge2@1.4.1: {} 1886 | 1887 | micromatch@4.0.8: 1888 | dependencies: 1889 | braces: 3.0.3 1890 | picomatch: 2.3.1 1891 | 1892 | mini-svg-data-uri@1.4.4: {} 1893 | 1894 | minimatch@3.1.2: 1895 | dependencies: 1896 | brace-expansion: 1.1.11 1897 | 1898 | minimatch@9.0.5: 1899 | dependencies: 1900 | brace-expansion: 2.0.1 1901 | 1902 | minipass@3.3.6: 1903 | dependencies: 1904 | yallist: 4.0.0 1905 | 1906 | minipass@5.0.0: {} 1907 | 1908 | minipass@7.1.2: {} 1909 | 1910 | minizlib@2.1.2: 1911 | dependencies: 1912 | minipass: 3.3.6 1913 | yallist: 4.0.0 1914 | 1915 | mkdirp@1.0.4: {} 1916 | 1917 | ms@2.1.3: {} 1918 | 1919 | mz@2.7.0: 1920 | dependencies: 1921 | any-promise: 1.3.0 1922 | object-assign: 4.1.1 1923 | thenify-all: 1.6.0 1924 | 1925 | nanoid@3.3.7: {} 1926 | 1927 | next-auth@5.0.0-beta.21(next@14.2.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): 1928 | dependencies: 1929 | '@auth/core': 0.35.0 1930 | next: 14.2.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 1931 | react: 18.3.1 1932 | 1933 | next@14.2.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1): 1934 | dependencies: 1935 | '@next/env': 14.2.13 1936 | '@swc/helpers': 0.5.5 1937 | busboy: 1.6.0 1938 | caniuse-lite: 1.0.30001680 1939 | graceful-fs: 4.2.11 1940 | postcss: 8.4.31 1941 | react: 18.3.1 1942 | react-dom: 18.3.1(react@18.3.1) 1943 | styled-jsx: 5.1.1(react@18.3.1) 1944 | optionalDependencies: 1945 | '@next/swc-darwin-arm64': 14.2.13 1946 | '@next/swc-darwin-x64': 14.2.13 1947 | '@next/swc-linux-arm64-gnu': 14.2.13 1948 | '@next/swc-linux-arm64-musl': 14.2.13 1949 | '@next/swc-linux-x64-gnu': 14.2.13 1950 | '@next/swc-linux-x64-musl': 14.2.13 1951 | '@next/swc-win32-arm64-msvc': 14.2.13 1952 | '@next/swc-win32-ia32-msvc': 14.2.13 1953 | '@next/swc-win32-x64-msvc': 14.2.13 1954 | transitivePeerDependencies: 1955 | - '@babel/core' 1956 | - babel-plugin-macros 1957 | 1958 | node-addon-api@5.1.0: {} 1959 | 1960 | node-fetch@2.7.0: 1961 | dependencies: 1962 | whatwg-url: 5.0.0 1963 | 1964 | node-gyp-build@4.8.2: {} 1965 | 1966 | node-releases@2.0.18: {} 1967 | 1968 | nopt@5.0.0: 1969 | dependencies: 1970 | abbrev: 1.1.1 1971 | 1972 | normalize-path@3.0.0: {} 1973 | 1974 | normalize-range@0.1.2: {} 1975 | 1976 | npmlog@5.0.1: 1977 | dependencies: 1978 | are-we-there-yet: 2.0.0 1979 | console-control-strings: 1.1.0 1980 | gauge: 3.0.2 1981 | set-blocking: 2.0.0 1982 | 1983 | oauth4webapi@2.17.0: {} 1984 | 1985 | object-assign@4.1.1: {} 1986 | 1987 | object-hash@3.0.0: {} 1988 | 1989 | obuf@1.1.2: {} 1990 | 1991 | on-exit-leak-free@2.1.2: {} 1992 | 1993 | once@1.4.0: 1994 | dependencies: 1995 | wrappy: 1.0.2 1996 | 1997 | package-json-from-dist@1.0.1: {} 1998 | 1999 | path-is-absolute@1.0.1: {} 2000 | 2001 | path-key@3.1.1: {} 2002 | 2003 | path-parse@1.0.7: {} 2004 | 2005 | path-scurry@1.11.1: 2006 | dependencies: 2007 | lru-cache: 10.4.3 2008 | minipass: 7.1.2 2009 | 2010 | pg-int8@1.0.1: {} 2011 | 2012 | pg-numeric@1.0.2: {} 2013 | 2014 | pg-protocol@1.7.0: {} 2015 | 2016 | pg-types@4.0.2: 2017 | dependencies: 2018 | pg-int8: 1.0.1 2019 | pg-numeric: 1.0.2 2020 | postgres-array: 3.0.2 2021 | postgres-bytea: 3.0.0 2022 | postgres-date: 2.1.0 2023 | postgres-interval: 3.0.0 2024 | postgres-range: 1.1.4 2025 | 2026 | picocolors@1.1.1: {} 2027 | 2028 | picomatch@2.3.1: {} 2029 | 2030 | pify@2.3.0: {} 2031 | 2032 | pino-abstract-transport@1.2.0: 2033 | dependencies: 2034 | readable-stream: 4.5.2 2035 | split2: 4.2.0 2036 | 2037 | pino-std-serializers@7.0.0: {} 2038 | 2039 | pino@9.4.0: 2040 | dependencies: 2041 | atomic-sleep: 1.0.0 2042 | fast-redact: 3.5.0 2043 | on-exit-leak-free: 2.1.2 2044 | pino-abstract-transport: 1.2.0 2045 | pino-std-serializers: 7.0.0 2046 | process-warning: 4.0.0 2047 | quick-format-unescaped: 4.0.4 2048 | real-require: 0.2.0 2049 | safe-stable-stringify: 2.5.0 2050 | sonic-boom: 4.2.0 2051 | thread-stream: 3.1.0 2052 | 2053 | pirates@4.0.6: {} 2054 | 2055 | postcss-import@15.1.0(postcss@8.4.47): 2056 | dependencies: 2057 | postcss: 8.4.47 2058 | postcss-value-parser: 4.2.0 2059 | read-cache: 1.0.0 2060 | resolve: 1.22.8 2061 | 2062 | postcss-js@4.0.1(postcss@8.4.47): 2063 | dependencies: 2064 | camelcase-css: 2.0.1 2065 | postcss: 8.4.47 2066 | 2067 | postcss-load-config@4.0.2(postcss@8.4.47): 2068 | dependencies: 2069 | lilconfig: 3.1.2 2070 | yaml: 2.6.0 2071 | optionalDependencies: 2072 | postcss: 8.4.47 2073 | 2074 | postcss-nested@6.2.0(postcss@8.4.47): 2075 | dependencies: 2076 | postcss: 8.4.47 2077 | postcss-selector-parser: 6.1.2 2078 | 2079 | postcss-selector-parser@6.1.2: 2080 | dependencies: 2081 | cssesc: 3.0.0 2082 | util-deprecate: 1.0.2 2083 | 2084 | postcss-value-parser@4.2.0: {} 2085 | 2086 | postcss@8.4.31: 2087 | dependencies: 2088 | nanoid: 3.3.7 2089 | picocolors: 1.1.1 2090 | source-map-js: 1.2.1 2091 | 2092 | postcss@8.4.47: 2093 | dependencies: 2094 | nanoid: 3.3.7 2095 | picocolors: 1.1.1 2096 | source-map-js: 1.2.1 2097 | 2098 | postgres-array@3.0.2: {} 2099 | 2100 | postgres-bytea@3.0.0: 2101 | dependencies: 2102 | obuf: 1.1.2 2103 | 2104 | postgres-date@2.1.0: {} 2105 | 2106 | postgres-interval@3.0.0: {} 2107 | 2108 | postgres-range@1.1.4: {} 2109 | 2110 | preact-render-to-string@5.2.3(preact@10.11.3): 2111 | dependencies: 2112 | preact: 10.11.3 2113 | pretty-format: 3.8.0 2114 | 2115 | preact@10.11.3: {} 2116 | 2117 | prettier@3.3.3: {} 2118 | 2119 | pretty-format@3.8.0: {} 2120 | 2121 | process-warning@4.0.0: {} 2122 | 2123 | process@0.11.10: {} 2124 | 2125 | queue-microtask@1.2.3: {} 2126 | 2127 | quick-format-unescaped@4.0.4: {} 2128 | 2129 | react-dom@18.3.1(react@18.3.1): 2130 | dependencies: 2131 | loose-envify: 1.4.0 2132 | react: 18.3.1 2133 | scheduler: 0.23.2 2134 | 2135 | react@18.3.1: 2136 | dependencies: 2137 | loose-envify: 1.4.0 2138 | 2139 | read-cache@1.0.0: 2140 | dependencies: 2141 | pify: 2.3.0 2142 | 2143 | readable-stream@3.6.2: 2144 | dependencies: 2145 | inherits: 2.0.4 2146 | string_decoder: 1.3.0 2147 | util-deprecate: 1.0.2 2148 | 2149 | readable-stream@4.5.2: 2150 | dependencies: 2151 | abort-controller: 3.0.0 2152 | buffer: 6.0.3 2153 | events: 3.3.0 2154 | process: 0.11.10 2155 | string_decoder: 1.3.0 2156 | 2157 | readdirp@3.6.0: 2158 | dependencies: 2159 | picomatch: 2.3.1 2160 | 2161 | real-require@0.2.0: {} 2162 | 2163 | resolve@1.22.8: 2164 | dependencies: 2165 | is-core-module: 2.15.1 2166 | path-parse: 1.0.7 2167 | supports-preserve-symlinks-flag: 1.0.0 2168 | 2169 | reusify@1.0.4: {} 2170 | 2171 | rimraf@3.0.2: 2172 | dependencies: 2173 | glob: 7.2.3 2174 | 2175 | run-parallel@1.2.0: 2176 | dependencies: 2177 | queue-microtask: 1.2.3 2178 | 2179 | safe-buffer@5.2.1: {} 2180 | 2181 | safe-stable-stringify@2.5.0: {} 2182 | 2183 | scheduler@0.23.2: 2184 | dependencies: 2185 | loose-envify: 1.4.0 2186 | 2187 | semver@6.3.1: {} 2188 | 2189 | semver@7.6.3: {} 2190 | 2191 | set-blocking@2.0.0: {} 2192 | 2193 | shebang-command@2.0.0: 2194 | dependencies: 2195 | shebang-regex: 3.0.0 2196 | 2197 | shebang-regex@3.0.0: {} 2198 | 2199 | signal-exit@3.0.7: {} 2200 | 2201 | signal-exit@4.1.0: {} 2202 | 2203 | sonic-boom@4.2.0: 2204 | dependencies: 2205 | atomic-sleep: 1.0.0 2206 | 2207 | source-map-js@1.2.1: {} 2208 | 2209 | source-map@0.5.7: {} 2210 | 2211 | split2@4.2.0: {} 2212 | 2213 | streamsearch@1.1.0: {} 2214 | 2215 | string-width@4.2.3: 2216 | dependencies: 2217 | emoji-regex: 8.0.0 2218 | is-fullwidth-code-point: 3.0.0 2219 | strip-ansi: 6.0.1 2220 | 2221 | string-width@5.1.2: 2222 | dependencies: 2223 | eastasianwidth: 0.2.0 2224 | emoji-regex: 9.2.2 2225 | strip-ansi: 7.1.0 2226 | 2227 | string_decoder@1.3.0: 2228 | dependencies: 2229 | safe-buffer: 5.2.1 2230 | 2231 | strip-ansi@6.0.1: 2232 | dependencies: 2233 | ansi-regex: 5.0.1 2234 | 2235 | strip-ansi@7.1.0: 2236 | dependencies: 2237 | ansi-regex: 6.1.0 2238 | 2239 | styled-jsx@5.1.1(react@18.3.1): 2240 | dependencies: 2241 | client-only: 0.0.1 2242 | react: 18.3.1 2243 | 2244 | sucrase@3.35.0: 2245 | dependencies: 2246 | '@jridgewell/gen-mapping': 0.3.5 2247 | commander: 4.1.1 2248 | glob: 10.4.5 2249 | lines-and-columns: 1.2.4 2250 | mz: 2.7.0 2251 | pirates: 4.0.6 2252 | ts-interface-checker: 0.1.13 2253 | 2254 | supports-preserve-symlinks-flag@1.0.0: {} 2255 | 2256 | tailwindcss@3.4.13: 2257 | dependencies: 2258 | '@alloc/quick-lru': 5.2.0 2259 | arg: 5.0.2 2260 | chokidar: 3.6.0 2261 | didyoumean: 1.2.2 2262 | dlv: 1.1.3 2263 | fast-glob: 3.3.2 2264 | glob-parent: 6.0.2 2265 | is-glob: 4.0.3 2266 | jiti: 1.21.6 2267 | lilconfig: 2.1.0 2268 | micromatch: 4.0.8 2269 | normalize-path: 3.0.0 2270 | object-hash: 3.0.0 2271 | picocolors: 1.1.1 2272 | postcss: 8.4.47 2273 | postcss-import: 15.1.0(postcss@8.4.47) 2274 | postcss-js: 4.0.1(postcss@8.4.47) 2275 | postcss-load-config: 4.0.2(postcss@8.4.47) 2276 | postcss-nested: 6.2.0(postcss@8.4.47) 2277 | postcss-selector-parser: 6.1.2 2278 | resolve: 1.22.8 2279 | sucrase: 3.35.0 2280 | transitivePeerDependencies: 2281 | - ts-node 2282 | 2283 | tar@6.2.1: 2284 | dependencies: 2285 | chownr: 2.0.0 2286 | fs-minipass: 2.1.0 2287 | minipass: 5.0.0 2288 | minizlib: 2.1.2 2289 | mkdirp: 1.0.4 2290 | yallist: 4.0.0 2291 | 2292 | thenify-all@1.6.0: 2293 | dependencies: 2294 | thenify: 3.3.1 2295 | 2296 | thenify@3.3.1: 2297 | dependencies: 2298 | any-promise: 1.3.0 2299 | 2300 | thread-stream@3.1.0: 2301 | dependencies: 2302 | real-require: 0.2.0 2303 | 2304 | to-fast-properties@2.0.0: {} 2305 | 2306 | to-regex-range@5.0.1: 2307 | dependencies: 2308 | is-number: 7.0.0 2309 | 2310 | tr46@0.0.3: {} 2311 | 2312 | ts-interface-checker@0.1.13: {} 2313 | 2314 | tslib@2.8.1: {} 2315 | 2316 | typescript@5.6.2: {} 2317 | 2318 | undici-types@6.20.0: {} 2319 | 2320 | update-browserslist-db@1.1.1(browserslist@4.24.2): 2321 | dependencies: 2322 | browserslist: 4.24.2 2323 | escalade: 3.2.0 2324 | picocolors: 1.1.1 2325 | 2326 | use-debounce@10.0.3(react@18.3.1): 2327 | dependencies: 2328 | react: 18.3.1 2329 | 2330 | util-deprecate@1.0.2: {} 2331 | 2332 | webidl-conversions@3.0.1: {} 2333 | 2334 | whatwg-url@5.0.0: 2335 | dependencies: 2336 | tr46: 0.0.3 2337 | webidl-conversions: 3.0.1 2338 | 2339 | which@2.0.2: 2340 | dependencies: 2341 | isexe: 2.0.0 2342 | 2343 | wide-align@1.1.5: 2344 | dependencies: 2345 | string-width: 4.2.3 2346 | 2347 | wrap-ansi@7.0.0: 2348 | dependencies: 2349 | ansi-styles: 4.3.0 2350 | string-width: 4.2.3 2351 | strip-ansi: 6.0.1 2352 | 2353 | wrap-ansi@8.1.0: 2354 | dependencies: 2355 | ansi-styles: 6.2.1 2356 | string-width: 5.1.2 2357 | strip-ansi: 7.1.0 2358 | 2359 | wrappy@1.0.2: {} 2360 | 2361 | ws@8.18.0(bufferutil@4.0.8): 2362 | optionalDependencies: 2363 | bufferutil: 4.0.8 2364 | 2365 | yallist@4.0.0: {} 2366 | 2367 | yaml@2.6.0: {} 2368 | 2369 | zod@3.23.8: {} 2370 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/customers/amy-burns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/amy-burns.png -------------------------------------------------------------------------------- /public/customers/balazs-orban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/balazs-orban.png -------------------------------------------------------------------------------- /public/customers/delba-de-oliveira.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/delba-de-oliveira.png -------------------------------------------------------------------------------- /public/customers/emil-kowalski.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/emil-kowalski.png -------------------------------------------------------------------------------- /public/customers/evil-rabbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/evil-rabbit.png -------------------------------------------------------------------------------- /public/customers/guillermo-rauch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/guillermo-rauch.png -------------------------------------------------------------------------------- /public/customers/hector-simpson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/hector-simpson.png -------------------------------------------------------------------------------- /public/customers/jared-palmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/jared-palmer.png -------------------------------------------------------------------------------- /public/customers/lee-robinson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/lee-robinson.png -------------------------------------------------------------------------------- /public/customers/michael-novotny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/michael-novotny.png -------------------------------------------------------------------------------- /public/customers/steph-dietz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/steph-dietz.png -------------------------------------------------------------------------------- /public/customers/steven-tey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/customers/steven-tey.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/favicon.ico -------------------------------------------------------------------------------- /public/hero-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/hero-desktop.png -------------------------------------------------------------------------------- /public/hero-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/hero-mobile.png -------------------------------------------------------------------------------- /public/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellydn/learn-nextjs14-dashboard/dd8998077f83f29d3db532a8891e57465079d4d6/public/opengraph-image.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | "group:allNonMajor", 6 | ":pinAllExceptPeerDependencies" 7 | ], 8 | "lockFileMaintenance": { 9 | "enabled": true 10 | }, 11 | "automerge": true, 12 | "major": { 13 | "automerge": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/seed.ts: -------------------------------------------------------------------------------- 1 | import logger from '@/app/lib/logger.js' 2 | import { type VercelPoolClient, db } from '@vercel/postgres' 3 | import bcrypt from 'bcrypt' 4 | import 'dotenv/config' 5 | 6 | import { 7 | customers, 8 | invoices, 9 | revenue, 10 | users, 11 | } from '../app/lib/placeholder-data.js' 12 | 13 | async function seedUsers(client: VercelPoolClient) { 14 | try { 15 | await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"` 16 | // Create the "invoices" table if it doesn't exist 17 | const createTable = await client.sql` 18 | CREATE TABLE IF NOT EXISTS users ( 19 | id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, 20 | name VARCHAR(255) NOT NULL, 21 | email TEXT NOT NULL UNIQUE, 22 | password TEXT NOT NULL 23 | ); 24 | ` 25 | 26 | logger.info(`Created "users" table`) 27 | 28 | // Insert data into the "users" table 29 | const insertedUsers = await Promise.all( 30 | users.map(async (user) => { 31 | const hashedPassword = await bcrypt.hash(user.password, 10) 32 | return client.sql` 33 | INSERT INTO users (id, name, email, password) 34 | VALUES (${user.id}, ${user.name}, ${user.email}, ${hashedPassword}) 35 | ON CONFLICT (id) DO NOTHING; 36 | ` 37 | }), 38 | ) 39 | 40 | logger.info(`Seeded ${insertedUsers.length} users`) 41 | 42 | return { 43 | createTable, 44 | users: insertedUsers, 45 | } 46 | } catch (error) { 47 | logger.error('Error seeding users:', error) 48 | throw error 49 | } 50 | } 51 | 52 | async function seedInvoices(client: VercelPoolClient) { 53 | try { 54 | await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"` 55 | 56 | // Create the "invoices" table if it doesn't exist 57 | const createTable = await client.sql` 58 | CREATE TABLE IF NOT EXISTS invoices ( 59 | id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, 60 | customer_id UUID NOT NULL, 61 | amount INT NOT NULL, 62 | status VARCHAR(255) NOT NULL, 63 | date DATE NOT NULL 64 | ); 65 | ` 66 | 67 | logger.info(`Created "invoices" table`) 68 | 69 | // Insert data into the "invoices" table 70 | const insertedInvoices = await Promise.all( 71 | invoices.map( 72 | (invoice) => client.sql` 73 | INSERT INTO invoices (customer_id, amount, status, date) 74 | VALUES (${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date}) 75 | ON CONFLICT (id) DO NOTHING; 76 | `, 77 | ), 78 | ) 79 | 80 | logger.info(`Seeded ${insertedInvoices.length} invoices`) 81 | 82 | return { 83 | createTable, 84 | invoices: insertedInvoices, 85 | } 86 | } catch (error) { 87 | logger.error('Error seeding invoices:', error) 88 | throw error 89 | } 90 | } 91 | 92 | async function seedCustomers(client: VercelPoolClient) { 93 | try { 94 | await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"` 95 | 96 | // Create the "customers" table if it doesn't exist 97 | const createTable = await client.sql` 98 | CREATE TABLE IF NOT EXISTS customers ( 99 | id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, 100 | name VARCHAR(255) NOT NULL, 101 | email VARCHAR(255) NOT NULL, 102 | image_url VARCHAR(255) NOT NULL 103 | ); 104 | ` 105 | 106 | logger.info(`Created "customers" table`) 107 | 108 | // Insert data into the "customers" table 109 | const insertedCustomers = await Promise.all( 110 | customers.map( 111 | (customer) => client.sql` 112 | INSERT INTO customers (id, name, email, image_url) 113 | VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url}) 114 | ON CONFLICT (id) DO NOTHING; 115 | `, 116 | ), 117 | ) 118 | 119 | logger.info(`Seeded ${insertedCustomers.length} customers`) 120 | 121 | return { 122 | createTable, 123 | customers: insertedCustomers, 124 | } 125 | } catch (error) { 126 | logger.error('Error seeding customers:', error) 127 | throw error 128 | } 129 | } 130 | 131 | async function seedRevenue(client: VercelPoolClient) { 132 | try { 133 | // Create the "revenue" table if it doesn't exist 134 | const createTable = await client.sql` 135 | CREATE TABLE IF NOT EXISTS revenue ( 136 | month VARCHAR(4) NOT NULL UNIQUE, 137 | revenue INT NOT NULL 138 | ); 139 | ` 140 | 141 | logger.info(`Created "revenue" table`) 142 | 143 | // Insert data into the "revenue" table 144 | const insertedRevenue = await Promise.all( 145 | revenue.map( 146 | (rev) => client.sql` 147 | INSERT INTO revenue (month, revenue) 148 | VALUES (${rev.month}, ${rev.revenue}) 149 | ON CONFLICT (month) DO NOTHING; 150 | `, 151 | ), 152 | ) 153 | 154 | logger.info(`Seeded ${insertedRevenue.length} revenue`) 155 | 156 | return { 157 | createTable, 158 | revenue: insertedRevenue, 159 | } 160 | } catch (error) { 161 | logger.error('Error seeding revenue:', error) 162 | throw error 163 | } 164 | } 165 | 166 | async function main() { 167 | logger.info('Seeding database...') 168 | const client = await db.connect() 169 | 170 | await seedUsers(client) 171 | await seedCustomers(client) 172 | await seedInvoices(client) 173 | await seedRevenue(client) 174 | 175 | // @ts-ignore 176 | await client.end() 177 | } 178 | 179 | main().catch((err) => { 180 | logger.error('An error occurred while attempting to seed the database:', err) 181 | }) 182 | -------------------------------------------------------------------------------- /sweep.yaml: -------------------------------------------------------------------------------- 1 | # Sweep AI turns bugs & feature requests into code changes (https://sweep.dev) 2 | # For details on our config file, check out our docs at https://docs.sweep.dev/usage/config 3 | 4 | # This setting contains a list of rules that Sweep will check for. If any of these rules are broken in a new commit, Sweep will create an pull request to fix the broken rule. 5 | rules: 6 | - "All docstrings and comments should be up to date." 7 | - "Do not include personal notes or comments in commit messages." 8 | - "Commit messages should clearly describe the changes made in the commit." 9 | - "Do not include irrelevant or incomplete information in commit messages." 10 | - "Code files should have a clear and consistent structure." 11 | - "Use appropriate file and variable naming conventions." 12 | 13 | # This is the branch that Sweep will develop from and make pull requests to. Most people use 'main' or 'master' but some users also use 'dev' or 'staging'. 14 | branch: 'main' 15 | 16 | # By default Sweep will read the logs and outputs from your existing Github Actions. To disable this, set this to false. 17 | gha_enabled: True 18 | 19 | # This is the description of your project. It will be used by sweep when creating PRs. You can tell Sweep what's unique about your project, what frameworks you use, or anything else you want. 20 | # 21 | # Example: 22 | # 23 | # description: sweepai/sweep is a python project. The main api endpoints are in sweepai/api.py. Write code that adheres to PEP8. 24 | description: '' 25 | 26 | # This sets whether to create pull requests as drafts. If this is set to True, then all pull requests will be created as drafts and GitHub Actions will not be triggered. 27 | draft: False 28 | 29 | # This is a list of directories that Sweep will not be able to edit. 30 | blocked_dirs: [] 31 | 32 | # This is a list of documentation links that Sweep will use to help it understand your code. You can add links to documentation for any packages you use here. 33 | # 34 | # Example: 35 | # 36 | # docs: 37 | # - PyGitHub: ["https://pygithub.readthedocs.io/en/latest/", "We use pygithub to interact with the GitHub API"] 38 | docs: [] 39 | 40 | # Sandbox executes commands in a sandboxed environment to validate code changes after every edit to guarantee pristine code. For more details, see the [Sandbox](./sandbox) page. 41 | sandbox: 42 | install: 43 | - trunk init 44 | check: 45 | - trunk fmt {file_path} || return 0 46 | - trunk check --fix --print-failures {file_path} 47 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | gridTemplateColumns: { 12 | '13': 'repeat(13, minmax(0, 1fr))', 13 | }, 14 | colors: { 15 | blue: { 16 | 400: '#2589FE', 17 | 500: '#0070F3', 18 | 600: '#2F6FEB', 19 | }, 20 | }, 21 | }, 22 | keyframes: { 23 | shimmer: { 24 | '100%': { 25 | transform: 'translateX(100%)', 26 | }, 27 | }, 28 | }, 29 | }, 30 | 31 | plugins: [require('@tailwindcss/forms')], 32 | } 33 | export default config 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | ".next/types/**/*.ts", 30 | "app/lib/placeholder-data.js", 31 | "scripts/seed.ts" 32 | ], 33 | "exclude": ["node_modules"] 34 | } 35 | --------------------------------------------------------------------------------