diff --git a/.env.example b/.env.example index 743cb24..ecea310 100644 --- a/.env.example +++ b/.env.example @@ -1,16 +1,18 @@ # App NEXT_PUBLIC_APP_URL=http://localhost:3000 +# Optional: comma-separated custom dev origins for Next.js allowedDevOrigins +# Leave empty unless you access the dev server from non-default origins (e.g. ngrok) +# Example: abc123.ngrok-free.dev,*.local-origin.dev +NEXT_ALLOWED_DEV_ORIGINS= # Supabase NEXT_PUBLIC_SUPABASE_URL= -NEXT_PUBLIC_SUPABASE_ANON_KEY= +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY= SUPABASE_SERVICE_ROLE_KEY= # Creem CREEM_API_KEY= CREEM_WEBHOOK_SECRET= +NEXT_PUBLIC_CREEM_PRODUCT_ID_STARTER= NEXT_PUBLIC_CREEM_PRODUCT_ID_PRO= -NEXT_PUBLIC_CREEM_PRODUCT_ID_CREDITS= - -# OAuth (configured in Supabase dashboard) -# GOOGLE_CLIENT_ID and GITHUB_CLIENT_ID are set in Supabase, not here +NEXT_PUBLIC_CREEM_PRODUCT_ID_BUSINESS= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ff3807..6b4b3c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,3 +32,34 @@ jobs: cache: npm - run: npm ci - run: npm run lint + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run test + + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + env: + NEXT_PUBLIC_SUPABASE_URL: http://localhost:54321 + NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: placeholder + NEXT_PUBLIC_APP_URL: http://localhost:3000 + NEXT_PUBLIC_CREEM_PRODUCT_ID_PRO: placeholder + NEXT_PUBLIC_CREEM_PRODUCT_ID_BUSINESS: placeholder + NEXT_PUBLIC_CREEM_PRODUCT_ID_CREDITS: placeholder diff --git a/.gitignore b/.gitignore index d5d5b79..1acbca5 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,4 @@ pnpm-debug.log* next-env.d.ts # local -TODO.md +# TODO.md diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 5365c75..e0cecaf 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,10 +1,8 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", "endOfLine": "lf", - "semi": false, "singleQuote": true, "tabWidth": 2, - "trailingComma": "es5", "printWidth": 100, "sortTailwindcss": { "stylesheet": "src/app/globals.css", diff --git a/.oxlintrc.json b/.oxlintrc.json index da5c2a4..2e24502 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -7,13 +7,7 @@ "env": { "builtin": true }, - "ignorePatterns": [ - ".next/**", - "out/**", - "build/**", - "next-env.d.ts", - "**/*.mdx" - ], + "ignorePatterns": [".next/**", "out/**", "build/**", "next-env.d.ts", "**/*.mdx"], "rules": { "@next/next/google-font-display": "warn", "@next/next/google-font-preconnect": "warn", diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 55a9cbb..30e7a94 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -116,8 +116,27 @@ supabase/ ├── 001_profiles.sql # profiles + auth trigger ├── 002_subscriptions.sql # Creem subscription tracking └── 003_credits.sql # wallet + ledger + +tests/ # App-level and cross-feature test suites +├── integration/ # Multi-feature contracts (auth + billing + credits) +└── e2e/ # Playwright user journeys across route groups ``` +## Testing Strategy + +Use a hybrid testing layout so ownership is clear and suites scale with the codebase. + +- Colocate unit and component tests with the feature they validate (`src/features/**`) and with shared UI in `src/components/**`. +- Keep cross-feature integration tests in `tests/integration/` when behavior spans multiple domains. +- Keep end-to-end tests in `tests/e2e/` to validate full user journeys at the app boundary. + +### Example Test Paths + +- `src/features/auth/components/login-form.test.tsx` +- `src/features/billing/actions/create-checkout.test.ts` +- `tests/integration/subscription-lifecycle.test.ts` +- `tests/e2e/signup-to-upgrade.spec.ts` + ## Principles | Rule | How it's applied | @@ -127,6 +146,7 @@ supabase/ | `lib/` = zero business logic | Only infrastructure clients (Supabase, Creem SDK) | | `components/ui/` = shadcn only | No feature logic leaks into shared UI primitives | | Webhooks co-located with billing | `features/billing/webhooks/` not `app/api/webhooks/` | +| Tests follow feature boundaries | Unit/component tests are colocated; integration + e2e tests live in `tests/` | ## Why Feature-Based? diff --git a/README.md b/README.md index 9198430..3db1e5e 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,388 @@ -# Next.js + Supabase + Creem Starter + + -[![CI](https://github.com/ubergonmx/nextjs-supabase-creem-starter/actions/workflows/ci.yml/badge.svg)](https://github.com/ubergonmx/nextjs-supabase-creem-starter/actions/workflows/ci.yml) -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/ubergonmx/nextjs-supabase-creem-starter) +# CreemKit - Next.js + Supabase + Creem Starter + +[![CI](https://github.com/ubergonmx/creemkit/actions/workflows/ci.yml/badge.svg)](https://github.com/ubergonmx/creemkit/actions/workflows/ci.yml) ![Status](https://img.shields.io/badge/status-work--in--progress-yellow) ![License](https://img.shields.io/badge/license-MIT-blue) -> 🚧 This project is actively under development. - -Production-ready Next.js starter with Supabase auth and Creem payments pre-integrated. +The most comprehensive, production-ready Next.js starter with Supabase auth and Creem payments pre-integrated. Clone, configure, and start selling — no boilerplate to write. -## Tech Stack +## Live Demo + +> 🔗 **[creemkit.vercel.app](https://creemkit.vercel.app)** + +- Use Creem's test card: `4242 4242 4242 4242` (any future expiry, any CVC) +- Try with discount code: `CREEMKIT2026` + +## Quick Start + +### One-click deploy + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/ubergonmx/creemkit) + +### Run locally + +```bash +git clone https://github.com/ubergonmx/creemkit.git +cd creemkit +npm install +cp .env.example .env.local +# Fill in NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY at minimum +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000). + +--- + +## Table of Contents + +- [About](#about) + - [Tech Stack](#tech-stack) + - [Features](#features) + - [Webhook Integrations](#webhook-integrations) + - [Architecture](#architecture) + - [Developer Tips](#developer-tips) + - [Database Schema](#database-schema) + - [Testing](#testing) +- [Setup](#setup) + - [1. Set up the project](#1-set-up-the-project) + - [2. Set up Supabase](#2-set-up-supabase) + - [3. Set up Creem](#3-set-up-creem) + - [4. Run locally](#4-run-locally) + - [5. Deploy to Vercel](#5-deploy-to-vercel) + - [6. Post-Deployment Setup](#6-post-deployment-setup) + - [7. Verify Everything](#7-verify-everything) +- [Extras](#extras) + - [AI Skills](#ai-skills) + - [Always Check](#always-check) +- [Contributing](#contributing) +- [License](#license) + +--- + +## About + +### Tech Stack - **Framework** — [Next.js](https://nextjs.org) (App Router, Server Components) - **Auth & Database** — [Supabase](https://supabase.com) - **Payments** — [Creem](https://creem.io) - **Styling** — [Tailwind CSS](https://tailwindcss.com) -- **UI Components** — [shadcn/ui](https://ui.shadcn.com) +- **UI Components** — [shadcn/ui](https://ui.shadcn.com) (Base UI) +- **Icons** - [Tabler icons](https://tabler.io/icons) - **Language** — TypeScript (strict mode) +- **Deployment** - [Vercel](https://vercel.com) + +### Features + +- Next.js 16 App Router + Server Components foundation +- Supabase auth (email/password + Google + GitHub OAuth) +- Route protection using Next.js `proxy.ts` middleware pattern +- Creem checkout flow for Starter / Pro / Business plans +- Subscription management: upgrade, downgrade, cancel at period end, resume +- Creem customer portal access for self-service billing +- Credits wallet system with transaction history +- Atomic credit operations via Postgres RPCs (`spend_credits`, `add_credits`, `deduct_credits`) +- Strict TypeScript, Oxlint, Oxfmt, and CI checks +- Production-safe webhook processing with idempotency logging + +### Webhook Integrations + +Webhook endpoint: `POST /api/webhooks/creem` + +Implemented events: + +- `checkout.completed` +- `subscription.active` +- `subscription.paid` +- `subscription.past_due` +- `subscription.unpaid` +- `subscription.trialing` +- `subscription.canceled` +- `subscription.expired` +- `subscription.paused` +- `subscription.update` +- `refund.created` + +Security and reliability: + +- Signature verification via `CREEM_WEBHOOK_SECRET` +- Idempotency via `webhook_events` table (duplicate event protection) +- Subscription and credits sync against Supabase using service role client + +### Architecture + +The app uses a **feature-based structure** with a thin App Router layer. + +```text +src/app/ + (marketing)/page.tsx, pricing/page.tsx + (auth)/login/page.tsx, signup/page.tsx, auth/callback/route.ts + (dashboard)/dashboard/ + page.tsx + billing/page.tsx + credits/page.tsx + feature-1/page.tsx + feature-2/page.tsx + settings/account/page.tsx + settings/billing/page.tsx + api/webhooks/creem/route.ts + +src/features/ + auth/ # schemas, actions, hooks, login/signup/settings UI + billing/ # checkout/subscription/portal actions, webhook handlers, billing UI + credits/ # wallet actions, schema, balance/history UI + summarizer/ # real feature using credit spend + pro-showcase/ # paid-gated analytics showcase + dashboard/ # shell/navigation components + landing/ # marketing sections + +src/lib/ + supabase/{client,server,admin,middleware}.ts + creem/client.ts + utils.ts, errors.ts + +src/proxy.ts # Next.js 16 proxy entry point +``` + +Key design rules: + +- Keep business logic inside `src/features/*`; keep `src/app/*` route files thin. +- Use Server Actions for app mutations; reserve API routes for external callbacks only. +- Keep Supabase and Creem clients in `src/lib/*` as shared infrastructure. + +### Developer Tips + +#### Server Actions + +- Prefer Server Actions for user-triggered mutations; keep API routes for inbound external events. +- After non-redirect mutations, use both `revalidatePath()` and `refresh()` so cache and UI stay in sync. +- For redirect flows (login, checkout, portal), use `redirect()` and skip explicit refresh logic. +- In client components, call actions inside `startTransition` and surface pending/error states. -## Features +#### Supabase Clients -> Coming soon +- Use `createClient` (`src/lib/supabase/server.ts`) for user-scoped reads with RLS enforced. +- Use `createAdminClient` (`src/lib/supabase/admin.ts`) for trusted server-side writes (webhooks/subscription mutations). +- RLS-blocked writes can fail silently; always check query responses/errors on write paths. +- Never expose `SUPABASE_SERVICE_ROLE_KEY` to the browser. -## Getting Started +### Database Schema -### Prerequisites +Migrations live in `supabase/migrations/`: -- Node.js 20+ -- A [Supabase](https://supabase.com) project -- A [Creem](https://creem.io) account with at least one product created +- `001_profiles.sql` + - `profiles` table + - trigger to sync new `auth.users` rows + - `creem_customer_id` support +- `002_subscriptions.sql` + - `subscriptions` table for Creem subscription state + - status tracking, indexes, and RLS policies +- `003_credits.sql` + - `credits` wallet table + - `credit_transactions` ledger table + - atomic SQL RPCs for spend/add/deduct operations +- `004_webhook_events.sql` + - webhook idempotency table to prevent duplicate processing -### 1. Clone the repo +RLS is enabled across core user-facing tables. + +### Testing + +Current test coverage includes: + +- Webhook handler behavior and event mapping +- Credits spending action behavior +- Billing plan helper/type behavior +- Creem client helper behavior +- Protected route middleware/proxy behavior + +Run checks locally: ```bash -git clone https://github.com/ubergonmx/nextjs-supabase-creem-starter.git -cd nextjs-supabase-creem-starter -npm install +npm run lint +npm run typecheck +npm test +npm run build ``` -### 2. Configure environment variables +--- + +## Setup + +### 1. Set up the project ```bash +git clone https://github.com/ubergonmx/creemkit.git +cd creemkit +npm install cp .env.example .env.local ``` -Fill in `.env.local` with your credentials: +### 2. Set up Supabase + +1. Create a new [Supabase project](https://database.new) +2. Set these values in `.env.local`: + +- `NEXT_PUBLIC_SUPABASE_URL`: Copy this in **Project Overview** (should look like `https://urcryetpnmgoatkitnumxb.supabase.co`) +- `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`: Copy this in **Project Overview** (open dropdown) or go to **Project Settings** (bottom of left sidebar) > **API Keys** > copy the key under **Publishable key** (starts with `sb_publishable_...`). If you're using legacy, copy `anon public` key (starts with `eyJ...`) in "Legacy anon, service_role API keys" tab. +- `SUPABASE_SERVICE_ROLE_KEY`: Same page, copy the key under **Secret keys** (starts with `sb_secret_***`). If you're using legacy, copy `service_role` secret key. +- `NEXT_ALLOWED_DEV_ORIGINS` (optional): Comma-separated origins/hostnames for Next.js `allowedDevOrigins` in development. Leave empty unless you access the dev server from a non-default origin (e.g. ngrok). + +3. Run the SQL migrations + +- Go to **SQL Editor** in the left sidebar (terminal icon) +- Copy-paste and run each file in `supabase/migrations/` in order: + - `001_profiles.sql` + - `002_subscriptions.sql` + - `003_credits.sql` + - `004_webhook_events.sql` +- If you prefer using the Supabase CLI, run `supabase db push` + +4. Setting up auth providers and redirect URL: + +- Go to **Authentication** (left sidebar, lock icon) > under **CONFIGURATION** , click **Sign In / Providers** +- Under **Auth Providers**, enable sign in of the following: + - **Google**: + - Enabling should show a right sidebar, copy **Callback URL (for OAuth)** (should look like `https://urcryetpnmgoatkitnumxb.supabase.co/auth/v1/callback`) + - Create an OAuth app in [Google Cloud Console](https://console.cloud.google.com/apis/credentials) (setup Consent screen if you haven't already). During creation, under **Authorized redirect URIs**, click "+ Add URI" and paste the callback URL. + - Click "Create", download JSON for backup and copy the client ID and secret into the Supabase sidebar, then save. + - **GitHub**: + - Create an OAuth app in [GitHub Developer Settings](https://github.com/settings/developers) + - Copy-paste redirect URL from the Supabase sidebar (should look like `https://urcryetpnmgoatkitnumxb.supabase.co/auth/v1/callback`) into the GitHub app's **Authorization callback URL** field, then save. + - After saving, copy the client ID and secret into the Supabase sidebar, then save. +- On the same page, under **CONFIGURATION**, go to **URL Configuration** +- Under **Redirect URLs**, click **Add URL** +- Add: `http://localhost:3000/auth/callback` +- (For production URL, see step 5 below) + +### 3. Set up Creem + +1. Create an account at [creem.io](https://www.creem.io) or, if you don't have an existing store, [create a new one](https://www.creem.io/dashboard/create) +2. Enable **Test Mode** in the bottom-left of the sidebar +3. Go to **Developers > API & Webhooks** in the left sidebar +4. On the **API Keys** tab, click **+ Create API Key**, name it anything (e.g. `creemkit`), toggle **Full Access** on, click **Create Key**, and copy the key +5. Create three subscription products — go to **Commerce > Products** in the left sidebar, click **Create Product**. For each product: + - **Section 1 (Product Details)**: Enter the product name and description + - **Section 2 (Payment Details)**: Click the **Subscription** tab, set Currency to **USD**, enter the price, set Subscription interval to **Monthly**, Tax category to **Software as a Service** + - **Sections 3–6**: Skip (image, features, advanced options, and abandoned cart are all optional) + - Click **Create Product** + + Create all three: + + | **Product name** | **Price** | + | ---------------- | --------- | + | Starter | 9 | + | Pro | 19 | + | Business | 99 | + +6. After creating each product, copy its `prod_` ID (shown on the product detail page) into your `.env.local` + +### 4. Run locally + +```bash +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000). +Everything should work except for checkouts, which require webhooks to be set up. You can skip to #5 if you want to deploy to production. -| Variable | Where to find it | -| -------------------------------------- | --------------------------------- | -| `NEXT_PUBLIC_SUPABASE_URL` | Supabase → Project Settings → API | -| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase → Project Settings → API | -| `SUPABASE_SERVICE_ROLE_KEY` | Supabase → Project Settings → API | -| `CREEM_API_KEY` | Creem → Developer → API Keys | -| `CREEM_WEBHOOK_SECRET` | Creem → Developer → Webhooks | -| `NEXT_PUBLIC_CREEM_PRODUCT_ID_PRO` | Creem → Products | -| `NEXT_PUBLIC_CREEM_PRODUCT_ID_CREDITS` | Creem → Products | +#### Optional: Ngrok for local development with webhooks -### 3. Set up the database +1. Run `npx ngrok http 3000` to create a secure tunnel to your localhost +2. Copy the generated forwarding URL (should look like `https://abc123.ngrok-free.dev`) and do the following: -Run the migrations in order against your Supabase project: +- In `.env.local`, paste the URL to `NEXT_PUBLIC_APP_URL` +- In `.env.local`, set `NEXT_ALLOWED_DEV_ORIGINS=abc123.ngrok-free.dev` (or the full origin), and comma-separate values if you use multiple custom dev origins +- In Supabase, go to **Authentication > URL Configuration**, set the URL in **Site URL** and add it to **Redirect URLs** +- In Creem, go to **Developers > API & Webhooks**, click the **Webhooks** tab, click **+ Create Webhook**, enter `/api/webhooks/creem` (e.g. `https://abc123.ngrok-free.dev/api/webhooks/creem`), select "All events", and create. Copy the generated secret and add it to `CREEM_WEBHOOK_SECRET` in your `.env.local`. -```bash -# Using the Supabase CLI -supabase db push +3. Instead of `http://localhost:3000`, use the ngrok URL (e.g. `https://abc123.ngrok-free.dev`) to test your app with webhooks locally. + +### 5. Deploy to Vercel + +1. Make sure your project is pushed to a GitHub repository +2. Go to [vercel.com](https://vercel.com) and log in or register +3. Click **Add New...** > **Project** +4. Under **Import Git Repository**, select your repo and click **Import** +5. On the **New Project** setup page: + - **Framework Preset** should automatically detect **Next.js** — no need to change it + - **Root Directory** can stay as `./` + - Expand the **Environment Variables** section + - Enter each key-value pair from your `.env.local` file (key on the left, value on the right). You can skip `CREEM_WEBHOOK_SECRET` for now — this will be configured after the initial deployment +6. Click **Deploy** and wait for the build to complete +7. Once done, copy your production URL from the Vercel dashboard (e.g. `https://your-app.vercel.app`) + +### 6. Post-Deployment Setup + +Once you have your production URL, connect the remaining services: + +1. **Supabase Auth URLs**: Navigate to **Supabase > Authentication > URL Configuration**: + - Set **Site URL** to `https://your-app.vercel.app` + - Under **Redirect URLs**, add `https://your-app.vercel.app/auth/callback` +2. **App URL env var**: In Vercel, go to **Settings > Environment Variables** and update `NEXT_PUBLIC_APP_URL` to your production URL +3. **Creem webhook**: In your Creem dashboard, go to **Developers > API & Webhooks**, open the **Webhooks** tab, and click **+ New**: + - **Name**: anything descriptive (e.g. `production`) + - **URL**: `https://your-app.vercel.app/api/webhooks/creem` + - **Events**: click **Select All** + - Click **Save** and copy the generated webhook secret +4. **Add webhook secret**: Back in Vercel **Settings > Environment Variables**, add `CREEM_WEBHOOK_SECRET` with the value from the previous step +5. **Redeploy**: Go to **Deployments**, click the three dots on the latest deployment, and hit **Redeploy** to apply the new environment variables + +### 7. Verify Everything + +Before deploying (or after major config changes), make sure everything passes: -# Or run each file manually in the Supabase SQL editor: -# supabase/migrations/001_profiles.sql -# supabase/migrations/002_subscriptions.sql -# supabase/migrations/003_credits.sql +```bash +npm run lint # lint +npm run typecheck # type-check +npm test # run tests +npm run build # production build ``` -### 4. Configure OAuth (optional) +If all checks pass, you're ready to ship. + +--- + +## Extras + +### AI Skills -Enable Google and GitHub providers in your Supabase dashboard under -**Authentication → Providers**. +Skills that help you (and AI) build faster when adding features: -### 5. Run the dev server +#### Supabase Best Practices (Query, RLS policies, etc.) ```bash -npm run dev +npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill supabase-postgres-best-practices ``` -Open [http://localhost:3000](http://localhost:3000). +#### Next.js + Supabase Best Practices + +```bash +npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill nextjs-supabase-auth +``` + +#### UI + +Since this starter uses shadcn/ui, check out the [shadcn registry directory](https://ui.shadcn.com/docs/directory) for more UI components and blocks. + +```bash +npx skills add raphaelsalaja/userinterface-wiki +npx skills add jakubkrehel/make-interfaces-feel-better +``` -## Deployment +### Always Check -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/ubergonmx/nextjs-supabase-creem-starter) +- **[vercel-doctor.com](https://vercel-doctor.com)** — if deploying to Vercel +- **[suparalph.vibeship.co](https://suparalph.vibeship.co/)** — scan for Supabase vulnerabilities -Set the same environment variables from `.env.example` in your Vercel project settings. +--- ## Contributing diff --git a/components.json b/components.json index 8b42db9..94bb384 100644 --- a/components.json +++ b/components.json @@ -21,9 +21,5 @@ "lib": "@/lib", "hooks": "@/hooks" }, - "registries": { - "@efferd": "https://efferd.com/r/{style}/{name}.json", - "@tailark": "https://tailark.com/r/{name}.json", - "@motion-primitives": "https://motion-primitives.com/c/{name}.json" - } + "registries": {} } diff --git a/next.config.ts b/next.config.ts index e607744..a368dec 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,22 +1,55 @@ -import createMDX from '@next/mdx' -import type { NextConfig } from 'next' +import createMDX from '@next/mdx'; +import type { NextConfig } from 'next'; + +const securityHeaders = [ + // Prevents MIME-type sniffing (CVE-2006-3396) + { key: 'X-Content-Type-Options', value: 'nosniff' }, + // Blocks the page from being embedded in iframes (clickjacking) + { key: 'X-Frame-Options', value: 'DENY' }, + // Controls how much referrer info is sent with requests + { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, + // Restricts access to browser features not used by the app + { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }, +]; + +const isProd = process.env.NODE_ENV === 'production'; +const allowedDevOrigins = (process.env.NEXT_ALLOWED_DEV_ORIGINS ?? '') + .split(',') + .map((origin) => origin.trim()) + .filter((origin) => origin.length > 0); const nextConfig: NextConfig = { reactStrictMode: true, + experimental: { + viewTransition: true, + }, pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'], + typescript: { + tsconfigPath: isProd ? 'tsconfig.build.json' : 'tsconfig.json', + }, images: { remotePatterns: [ // Add allowed image domains here, e.g.: // { protocol: 'https', hostname: 'avatars.githubusercontent.com' }, ], }, -} + // Configure dev-only trusted origins via NEXT_ALLOWED_DEV_ORIGINS (comma-separated). + allowedDevOrigins, + async headers() { + return [ + { + source: '/(.*)', + headers: securityHeaders, + }, + ]; + }, +}; const withMDX = createMDX({ options: { remarkPlugins: [], rehypePlugins: [], }, -}) +}); -export default withMDX(nextConfig) +export default withMDX(nextConfig); diff --git a/package-lock.json b/package-lock.json index aac896d..b8c2930 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,16 @@ { - "name": "nextjs-supabase-creem-starter", + "name": "creemkit", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "nextjs-supabase-creem-starter", + "name": "creemkit", "version": "0.0.1", "dependencies": { "@base-ui/react": "^1.3.0", + "@bprogress/next": "^3.2.12", + "@creem_io/nextjs": "^0.6.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", @@ -16,7 +18,8 @@ "@mdx-js/loader": "^3.1.1", "@mdx-js/react": "^3.1.1", "@next/mdx": "^16.2.1", - "@oyerinde/caliper": "^0.2.3", + "@supabase/ssr": "^0.9.0", + "@supabase/supabase-js": "^2.100.1", "@tabler/icons-react": "^3.41.0", "@tailwindcss/typography": "^0.5.19", "@tanstack/react-table": "^8.21.3", @@ -39,16 +42,29 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.2.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/node": "^25.5.0", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "jsdom": "^29.0.1", "oxfmt": "latest", "oxlint": "latest", "postcss": "^8", "tailwindcss": "^4.2.1", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.2" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -62,6 +78,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.1.tgz", + "integrity": "sha512-iGWN8E45Ws0XWx3D44Q1t6vX2LqhCKcwfmwBYCDsFrYFS6m4q/Ks61L2veETaLv+ckDC6+dTETJoaAAb7VjLiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz", + "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -529,6 +606,206 @@ } } }, + "node_modules/@bprogress/core": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@bprogress/core/-/core-1.3.4.tgz", + "integrity": "sha512-q/AqpurI/1uJzOrQROuZWixn/+ARekh+uvJGwLCP6HQ/EqAX4SkvNf618tSBxL4NysC0MwqAppb/mRw6Tzi61w==", + "license": "MIT" + }, + "node_modules/@bprogress/next": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/@bprogress/next/-/next-3.2.12.tgz", + "integrity": "sha512-/ZvNwbAd0ty9QiQwCfT2AfwWVdAaEyCPx5RUz3CfiiJS/OLBohhDz/IC/srhwK9GnXeXavvtiUrpKzN5GJDwlw==", + "license": "MIT", + "dependencies": { + "@bprogress/core": "^1.3.4", + "@bprogress/react": "^1.2.7" + }, + "peerDependencies": { + "next": ">=13.0.0", + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@bprogress/react": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@bprogress/react/-/react-1.2.7.tgz", + "integrity": "sha512-MqJfHW+R5CQeWqyqrLxUjdBRHk24Xl63OkBLo5DMWqUqocUikRTfCIc/jtQQbPk7BRfdr5OP3Lx7YlfQ9QOZMQ==", + "license": "MIT", + "dependencies": { + "@bprogress/core": "^1.3.4" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@creem_io/nextjs": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@creem_io/nextjs/-/nextjs-0.6.0.tgz", + "integrity": "sha512-ORUn7FBvf9kq2YyS18kexRf7x/kUw3W3vXD7nnWKWmGdT3/RbvMkPHqPoLOjZp30zEnig72y1C0ALe7tmZH6Kw==", + "license": "MIT", + "dependencies": { + "creem": "^1.3.6" + }, + "peerDependencies": { + "next": ">=13.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", @@ -826,6 +1103,24 @@ "tslib": "^2.4.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@floating-ui/core": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", @@ -1644,16 +1939,22 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@next/env": { @@ -1916,6 +2217,16 @@ "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", "license": "MIT" }, + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@oxfmt/binding-android-arm-eabi": { "version": "0.42.0", "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.42.0.tgz", @@ -2562,105 +2873,6 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oyerinde/caliper": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@oyerinde/caliper/-/caliper-0.2.3.tgz", - "integrity": "sha512-QtjyJMsWQUBZNZL81f57fD3jXMBPXM727MTq/cSHE3Aubttg3GuIp3m1YJQvZeBdj8SAg++kfz5uABC7axUz6Q==", - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "1.25.3", - "ws": "8.19.0", - "zod": "4.3.5" - }, - "bin": { - "caliper": "dist/mcp.js" - } - }, - "node_modules/@oyerinde/caliper/node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz", - "integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@oyerinde/caliper/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@oyerinde/caliper/node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/@oyerinde/caliper/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/@oyerinde/caliper/node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -3028,12 +3240,274 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "license": "MIT" - }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", @@ -3058,6 +3532,117 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, + "node_modules/@supabase/auth-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.1.tgz", + "integrity": "sha512-c5FB4nrG7cs1mLSzFGuIVl2iR2YO5XkSJ96uF4zubYm8YDn71XOi2emE9sBm/avfGCj61jaRBLOvxEAVnpys0Q==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.100.1.tgz", + "integrity": "sha512-mo8QheoV4KR+wSubtyEWhZUxWnCM7YZ23TncccMAlbWAHb8YTDqRGRm9IalWCAswniKyud6buZCk9snRqI86KA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz", + "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.100.1.tgz", + "integrity": "sha512-OIh4mOSo2LdqF2kox76OAPDtcSs+PwKABJOjc6plUV4/LXhFEsI2uwdEEIs7K7fd141qehWEVl/Y+Ts0fNvYsw==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.100.1.tgz", + "integrity": "sha512-FHuRWPX4qZQ4x+0Q+ZrKaBZnOiVGiwsgiAUJM98pYRib1yeaE/fOM1lZ1ozd+4gA8Udw23OyaD8SxKS5mT5NYw==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.0", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/ssr": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.9.0.tgz", + "integrity": "sha512-UFY6otYV3yqCgV+AyHj80vNkTvbf1Gas2LW4dpbQ4ap6p6v3eB2oaDfcI99jsuJzwVBCFU4BJI+oDYyhNk1z0Q==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.2" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.97.0" + } + }, + "node_modules/@supabase/ssr/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.100.1.tgz", + "integrity": "sha512-x9xpEIoWM4xKiAlwfWTgHPSN6N4Y0aS4FVU4F6ZPbq7Gayw08SrtC6/YH/gOr8CjXQr0HxXYXDop2xGTSjubYA==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.100.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.100.1.tgz", + "integrity": "sha512-CAeFm5sfX8sbTzxoxRafhohreIzl9a7R6qHTck3MrgTqm5M5g/u0IHfEKYzI9w/17r8NINl8UZrw2i08wrO7Iw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.100.1", + "@supabase/functions-js": "2.100.1", + "@supabase/postgrest-js": "2.100.1", + "@supabase/realtime-js": "2.100.1", + "@supabase/storage-js": "2.100.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -3422,6 +4007,81 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@ts-morph/common": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", @@ -3508,6 +4168,24 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -3580,6 +4258,13 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3629,7 +4314,6 @@ "version": "25.5.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" @@ -3666,23 +4350,171 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT" + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", + "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", + "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.2", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", + "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", + "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.2", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", + "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.2", + "@vitest/utils": "4.1.2", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@types/validate-npm-package-name": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", - "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", - "license": "MIT" + "node_modules/@vitest/spy": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", + "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" + "node_modules/@vitest/utils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", + "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.2", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, "node_modules/accepts": { "version": "2.0.0", @@ -3811,6 +4643,26 @@ "node": ">=10" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", @@ -3854,6 +4706,16 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -4015,6 +4877,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -4332,6 +5204,18 @@ } } }, + "node_modules/creem": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/creem/-/creem-1.4.4.tgz", + "integrity": "sha512-hfnHOIqNERf783FMJOOpbmD701y4t6HuVKR8kIm16X3l5OK6yMM0ifd2zY8CbRqSLeqK/iQV1D8FLgZEVs7Alg==", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.26.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "bin": { + "mcp": "bin/mcp-server.js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4346,6 +5230,27 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -4494,6 +5399,20 @@ "node": ">= 12" } }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4511,6 +5430,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", @@ -4649,6 +5575,13 @@ "node": ">=0.3.1" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dotenv": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", @@ -4727,6 +5660,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -4763,6 +5709,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -5007,6 +5960,16 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", @@ -5247,6 +6210,21 @@ "node": ">=14.14" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5500,6 +6478,19 @@ "node": ">=16.9.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -5542,6 +6533,15 @@ "node": ">=18.18.0" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -5593,6 +6593,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -5808,6 +6818,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -5908,6 +6925,57 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", + "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6291,6 +7359,16 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -6492,6 +7570,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -7183,6 +8268,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -7525,6 +8620,17 @@ "node": ">= 10" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -7774,6 +8880,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7804,6 +8923,13 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7884,6 +9010,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/pretty-ms": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", @@ -7944,6 +9115,16 @@ "node": ">= 0.10" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", @@ -8251,6 +9432,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -8393,6 +9588,47 @@ "node": ">=0.10.0" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "dev": true, + "license": "MIT" + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -8460,6 +9696,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -8773,6 +10022,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -8829,6 +10085,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -8838,6 +10101,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -8946,6 +10216,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -8987,6 +10270,13 @@ } } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tabbable": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", @@ -9041,6 +10331,71 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", @@ -9051,6 +10406,16 @@ "node": "^20.0.0 || >=22.0.0" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tldts": { "version": "7.0.27", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", @@ -9102,6 +10467,19 @@ "node": ">=16" } }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -9190,11 +10568,20 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "devOptional": true, "license": "MIT" }, "node_modules/unicorn-magic": { @@ -9505,6 +10892,205 @@ "d3-timer": "^3.0.1" } }, + "node_modules/vite": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", + "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.2", + "@vitest/mocker": "4.1.2", + "@vitest/pretty-format": "4.1.2", + "@vitest/runner": "4.1.2", + "@vitest/snapshot": "4.1.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.2", + "@vitest/browser-preview": "4.1.2", + "@vitest/browser-webdriverio": "4.1.2", + "@vitest/ui": "4.1.2", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -9514,6 +11100,41 @@ "node": ">= 8" } }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9529,6 +11150,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -9627,6 +11265,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 8ae35c5..70730a7 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "nextjs-supabase-creem-starter", + "name": "creemkit", "version": "0.0.1", "type": "module", "private": true, @@ -9,10 +9,14 @@ "start": "next start", "lint": "oxlint --format=github", "format": "oxfmt", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@base-ui/react": "^1.3.0", + "@bprogress/next": "^3.2.12", + "@creem_io/nextjs": "^0.6.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", @@ -20,7 +24,8 @@ "@mdx-js/loader": "^3.1.1", "@mdx-js/react": "^3.1.1", "@next/mdx": "^16.2.1", - "@oyerinde/caliper": "^0.2.3", + "@supabase/ssr": "^0.9.0", + "@supabase/supabase-js": "^2.100.1", "@tabler/icons-react": "^3.41.0", "@tailwindcss/typography": "^0.5.19", "@tanstack/react-table": "^8.21.3", @@ -43,14 +48,20 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.2.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/node": "^25.5.0", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "jsdom": "^29.0.1", "oxfmt": "latest", "oxlint": "latest", "postcss": "^8", "tailwindcss": "^4.2.1", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.2" }, "overrides": { "@types/react": "19.2.14", diff --git a/postcss.config.mjs b/postcss.config.mjs index a869506..5d6d845 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -3,6 +3,6 @@ const config = { plugins: { '@tailwindcss/postcss': {}, }, -} +}; -export default config +export default config; diff --git a/src/app/(auth)/auth/callback/route.ts b/src/app/(auth)/auth/callback/route.ts new file mode 100644 index 0000000..85c759d --- /dev/null +++ b/src/app/(auth)/auth/callback/route.ts @@ -0,0 +1,38 @@ +import { NextResponse, type NextRequest } from 'next/server'; +import { createClient } from '@/lib/supabase/server'; + +function getSafeNextPath(nextParam: string | null): string { + if (!nextParam) { + return '/dashboard'; + } + + if (!nextParam.startsWith('/') || nextParam.startsWith('//')) { + return '/dashboard'; + } + + return nextParam; +} + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const code = searchParams.get('code'); + const nextPath = getSafeNextPath(searchParams.get('next')); + + // Next.js sees requests as localhost even when tunnelled via ngrok. + // Use forwarded headers to redirect back to the actual origin. + const forwardedHost = request.headers.get('x-forwarded-host')?.split(',')[0]?.trim(); + const forwardedProto = request.headers.get('x-forwarded-proto')?.split(',')[0]?.trim(); + const origin = forwardedHost + ? `${forwardedProto ?? 'https'}://${forwardedHost}` + : new URL(request.url).origin; + + if (code) { + const supabase = await createClient(); + const { error } = await supabase.auth.exchangeCodeForSession(code); + if (!error) { + return NextResponse.redirect(new URL(nextPath, origin)); + } + } + + return NextResponse.redirect(new URL('/login?error=auth_callback_failed', origin)); +} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 8cb8ecb..024e07c 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,21 +1,18 @@ -import type { Metadata } from "next"; +import type { Metadata } from 'next'; +import { ProgressProvider } from '@/components/progress-provider'; export const metadata: Metadata = { title: { - template: "%s | Auth | CreemKit", - default: "Auth | CreemKit", + template: '%s | CreemKit', + default: 'Auth | CreemKit', }, - description: "Authentication pages for your CreemKit workspace.", + description: 'Authentication pages for your CreemKit workspace.', robots: { index: false, follow: false, }, }; -export default function AuthLayout({ - children, -}: { - children: React.ReactNode; -}) { - return <>{children}; +export default function AuthLayout({ children }: { children: React.ReactNode }) { + return {children}; } diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 2e46be8..e82fe6d 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,21 +1,31 @@ -import type { Metadata } from "next"; -import { LoginForm } from "@/features/auth/components/login-form"; -import { Logo } from "@/components/logo"; -import Link from "next/link"; +import type { Metadata } from 'next'; +import { LoginForm } from '@/features/auth/components/login-form'; +import { SignupPendingToast } from '@/features/auth/components/signup-pending-toast'; +import { UrlToast } from '@/components/url-toast'; +import { Logo } from '@/components/logo'; +import { LOGIN_ERRORS } from '@/features/auth/errors'; +import { resolveError } from '@/lib/errors'; +import Link from 'next/link'; export const metadata: Metadata = { - title: "Log in", - description: "Access your CreemKit account to manage subscriptions and credits.", + title: 'Log in', + description: 'Access your CreemKit account to manage subscriptions and credits.', }; -export default function LoginPage() { +export default async function LoginPage({ + searchParams, +}: { + searchParams: Promise<{ signup?: string; error?: string }>; +}) { + const { signup, error } = await searchParams; + const errorMessage = resolveError(error, LOGIN_ERRORS); + return (
+ {signup === 'pending' && } + {errorMessage && }
- + diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index d6574f5..7f852ec 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -1,21 +1,18 @@ -import type { Metadata } from "next"; -import { SignupForm } from "@/features/auth/components/signup-form"; -import { Logo } from "@/components/logo"; -import Link from "next/link"; +import type { Metadata } from 'next'; +import { SignupForm } from '@/features/auth/components/signup-form'; +import { Logo } from '@/components/logo'; +import Link from 'next/link'; export const metadata: Metadata = { - title: "Create account", - description: "Create your CreemKit account and start selling with Supabase and Creem.", + title: 'Create account', + description: 'Create your CreemKit account and start selling with Supabase and Creem.', }; export default function SignupPage() { return (
- + diff --git a/src/app/(dashboard)/dashboard/billing/page.tsx b/src/app/(dashboard)/dashboard/billing/page.tsx new file mode 100644 index 0000000..aa296c8 --- /dev/null +++ b/src/app/(dashboard)/dashboard/billing/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export default function BillingPage() { + redirect('/dashboard/settings/billing'); +} diff --git a/src/app/(dashboard)/dashboard/credits/page.tsx b/src/app/(dashboard)/dashboard/credits/page.tsx new file mode 100644 index 0000000..95ecc7a --- /dev/null +++ b/src/app/(dashboard)/dashboard/credits/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export default function CreditsPage() { + redirect('/dashboard/settings/billing'); +} diff --git a/src/app/(dashboard)/dashboard/feature-1/page.tsx b/src/app/(dashboard)/dashboard/feature-1/page.tsx new file mode 100644 index 0000000..2a6cf0c --- /dev/null +++ b/src/app/(dashboard)/dashboard/feature-1/page.tsx @@ -0,0 +1,31 @@ +import type { Metadata } from 'next'; +import { SiteHeader } from '@/features/dashboard/components/site-header'; +import { SummarizeForm } from '@/features/summarizer/components/summarize-form'; +import { getCreditsBalance } from '@/features/credits/actions/credits'; +import { getUserSubscription } from '@/features/billing/actions/subscription'; + +export const metadata: Metadata = { + title: 'AI Summarizer', + description: 'Summarize any text using AI. Each request costs 1 credit.', +}; + +export default async function AISummarizerPage() { + const [balance, subscription] = await Promise.all([ + getCreditsBalance(), + getUserSubscription(), + ]); + + const isActive = + subscription?.status === 'active' || subscription?.status === 'trialing'; + const isUnlimited = + isActive && subscription?.planId === process.env.NEXT_PUBLIC_CREEM_PRODUCT_ID_BUSINESS; + + return ( + <> + +
+ +
+ + ); +} diff --git a/src/app/(dashboard)/dashboard/feature-2/page.tsx b/src/app/(dashboard)/dashboard/feature-2/page.tsx new file mode 100644 index 0000000..bcf4be0 --- /dev/null +++ b/src/app/(dashboard)/dashboard/feature-2/page.tsx @@ -0,0 +1,43 @@ +import type { Metadata } from 'next'; +import { SiteHeader } from '@/features/dashboard/components/site-header'; +import { ProGate } from '@/features/pro-showcase/components/pro-gate'; +import { AnalyticsContent } from '@/features/pro-showcase/components/analytics-content'; +import { getUserSubscription } from '@/features/billing/actions/subscription'; +import { PLANS } from '@/features/billing/types'; + +export const metadata: Metadata = { + title: 'Advanced Analytics', + description: 'Premium analytics and conversion insights — Pro and Business plans only.', +}; + +export default async function AdvancedAnalyticsPage() { + const subscription = await getUserSubscription(); + + const isActive = + subscription?.status === 'active' || subscription?.status === 'trialing'; + + const isPro = + isActive && + (subscription?.planId === PLANS.pro.productId || + subscription?.planId === PLANS.business.productId); + + return ( + <> + +
+
+ {!isPro && ( +

+ A preview of the premium analytics available on{' '} + Pro and{' '} + Business plans. +

+ )} + + + +
+
+ + ); +} diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 1de055b..11e1a46 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -1,43 +1,59 @@ -import type { Metadata } from "next"; -import { AppSidebar } from "@/features/dashboard/components/app-sidebar"; -import { ChartAreaInteractive } from "@/features/dashboard/components/chart-area-interactive"; -import { DataTable } from "@/features/dashboard/components/data-table"; -import { SectionCards } from "@/features/dashboard/components/section-cards"; -import { SiteHeader } from "@/features/dashboard/components/site-header"; -import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; - -import data from "./data.json"; +import type { Metadata } from 'next'; +import { ChartAreaInteractive } from '@/features/dashboard/components/chart-area-interactive'; +import { SectionCards } from '@/features/dashboard/components/section-cards'; +import { SiteHeader } from '@/features/dashboard/components/site-header'; +import { SubscriptionCard } from '@/features/billing/components/subscription-card'; +import { CheckoutSuccessToast } from '@/features/billing/components/checkout-success-toast'; +import { getUserSubscription } from '@/features/billing/actions/subscription'; +import { getCreditsBalance } from '@/features/credits/actions/credits'; +import { planNameFromId } from '@/features/billing/types'; export const metadata: Metadata = { - title: "Overview", - description: "View your key metrics, usage data, and recent activity.", + title: 'Overview', + description: 'View your key metrics, usage data, and recent activity.', }; -export default function Page() { +export default async function Page({ + searchParams, +}: { + searchParams: Promise<{ checkout?: string }>; +}) { + const [{ checkout }, subscription, creditsBalance] = await Promise.all([ + searchParams, + getUserSubscription(), + getCreditsBalance(), + ]); + + const isActive = + subscription?.status === 'active' || subscription?.status === 'trialing'; + const isUnlimited = + isActive && subscription?.planId === process.env.NEXT_PUBLIC_CREEM_PRODUCT_ID_BUSINESS; + const planName = planNameFromId(isActive ? (subscription?.planId ?? null) : null); + return ( - - - - -
-
-
- -
- -
- + <> + {checkout === 'success' && } + +
+
+
+
+ +
+ +
+
- - +
+ ); } diff --git a/src/app/(dashboard)/dashboard/settings/_components/settings-nav.tsx b/src/app/(dashboard)/dashboard/settings/_components/settings-nav.tsx new file mode 100644 index 0000000..e7cc005 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/_components/settings-nav.tsx @@ -0,0 +1,33 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { cn } from '@/lib/utils'; + +const tabs = [ + { label: 'Account', href: '/dashboard/settings/account' }, + { label: 'Billing & Usage', href: '/dashboard/settings/billing' }, +]; + +export function SettingsNav() { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/src/app/(dashboard)/dashboard/settings/account/page.tsx b/src/app/(dashboard)/dashboard/settings/account/page.tsx new file mode 100644 index 0000000..5792f19 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/account/page.tsx @@ -0,0 +1,17 @@ +import type { Metadata } from 'next'; +import { getUser } from '@/lib/supabase/server'; +import { SettingsProfileCard } from '@/features/auth/components/settings-profile-card'; + +export const metadata: Metadata = { + title: 'Account', + description: 'Manage your account settings and preferences.', +}; + +export default async function AccountPage() { + const user = await getUser(); + + const fullName = (user?.user_metadata?.full_name as string) ?? ''; + const email = user?.email ?? ''; + + return ; +} diff --git a/src/app/(dashboard)/dashboard/settings/billing/page.tsx b/src/app/(dashboard)/dashboard/settings/billing/page.tsx new file mode 100644 index 0000000..d519589 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/billing/page.tsx @@ -0,0 +1,171 @@ +import type { Metadata } from 'next'; +import { getUserSubscription } from '@/features/billing/actions/subscription'; +import { getCreditsBalance, getCreditTransactions } from '@/features/credits/actions/credits'; +import { SubscriptionCard } from '@/features/billing/components/subscription-card'; +import { CreditsBalanceCard } from '@/features/credits/components/credits-balance-card'; +import { TransactionHistory } from '@/features/credits/components/transaction-history'; +import { CheckoutButton } from '@/features/billing/components/checkout-button'; +import { UpgradeButton } from '@/features/billing/components/upgrade-button'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { UrlToast } from '@/components/url-toast'; +import { CheckoutSuccessToast } from '@/features/billing/components/checkout-success-toast'; +import { PLANS, getPlanAction } from '@/features/billing/types'; +import { BILLING_ERRORS } from '@/features/billing/errors'; +import { resolveError } from '@/lib/errors'; + +export const metadata: Metadata = { + title: 'Billing & Usage', + description: 'Manage your subscription, billing details, and credit usage.', +}; + +export default async function BillingPage({ + searchParams, +}: { + searchParams: Promise<{ error?: string; checkout?: string }>; +}) { + const [{ error, checkout }, subscription, creditsBalance, transactions] = await Promise.all([ + searchParams, + getUserSubscription(), + getCreditsBalance(), + getCreditTransactions(50), + ]); + + const errorMessage = resolveError(error, BILLING_ERRORS); + const isActiveSub = + subscription?.status === 'active' || subscription?.status === 'trialing'; + const currentPlanId = isActiveSub ? (subscription?.planId ?? null) : null; + const isUnlimited = + isActiveSub && subscription?.planId === process.env.NEXT_PUBLIC_CREEM_PRODUCT_ID_BUSINESS; + + const starterPlan = PLANS.starter.productId + ? getPlanAction(currentPlanId, PLANS.starter.productId, PLANS.starter.price, PLANS.starter.name) + : null; + const proPlan = PLANS.pro.productId + ? getPlanAction(currentPlanId, PLANS.pro.productId, PLANS.pro.price, PLANS.pro.name) + : null; + const businessPlan = PLANS.business.productId + ? getPlanAction( + currentPlanId, + PLANS.business.productId, + PLANS.business.price, + PLANS.business.name, + ) + : null; + + return ( +
+ {checkout === 'success' && } + {errorMessage && } + {/* Top row: subscription status + credit balance side by side */} +
+ + +
+ + + + Available Plans + Upgrade or change your plan. + + +
+ {PLANS.starter.productId && starterPlan && ( +
+

{PLANS.starter.name}

+

+ ${(PLANS.starter.price / 100).toFixed(0)} + /mo +

+
    +
  • 250 credits / month
  • +
  • Webhook event logs
  • +
  • Email support
  • +
+
+ {starterPlan.disabled ? ( + + ) : isActiveSub ? ( + + {starterPlan.label} + + ) : ( + + {starterPlan.label} + + )} +
+
+ )} + + {PLANS.pro.productId && proPlan && ( +
+

{PLANS.pro.name}

+

+ ${(PLANS.pro.price / 100).toFixed(0)} + /mo +

+
    +
  • 5,000 credits / month
  • +
  • Priority email support
  • +
  • Advanced security features
  • +
+
+ {proPlan.disabled ? ( + + ) : isActiveSub ? ( + {proPlan.label} + ) : ( + + {proPlan.label} + + )} +
+
+ )} + + {PLANS.business.productId && businessPlan && ( +
+

{PLANS.business.name}

+

+ ${(PLANS.business.price / 100).toFixed(0)} + /mo +

+
    +
  • Unlimited credits
  • +
  • Dedicated support
  • +
  • SLA guarantee
  • +
+
+ {businessPlan.disabled ? ( + + ) : isActiveSub ? ( + + {businessPlan.label} + + ) : ( + + {businessPlan.label} + + )} +
+
+ )} +
+
+
+ + +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/settings/layout.tsx b/src/app/(dashboard)/dashboard/settings/layout.tsx new file mode 100644 index 0000000..2ef79cf --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/layout.tsx @@ -0,0 +1,15 @@ +import type { ReactNode } from 'react'; +import { SiteHeader } from '@/features/dashboard/components/site-header'; +import { SettingsNav } from './_components/settings-nav'; + +export default function SettingsLayout({ children }: { children: ReactNode }) { + return ( + <> + +
+ + {children} +
+ + ); +} diff --git a/src/app/(dashboard)/dashboard/settings/page.tsx b/src/app/(dashboard)/dashboard/settings/page.tsx new file mode 100644 index 0000000..ea1e631 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export default function SettingsPage() { + redirect('/dashboard/settings/account'); +} diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index 534fdd0..e92105c 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -1,21 +1,57 @@ -import type { Metadata } from "next"; +import type { Metadata } from 'next'; +import { getUser } from '@/lib/supabase/server'; +import { redirect } from 'next/navigation'; +import { AppSidebar } from '@/features/dashboard/components/app-sidebar'; +import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; +import { ProgressProvider } from '@/components/progress-provider'; +import { getUserSubscription } from '@/features/billing/actions/subscription'; +import { planNameFromId } from '@/features/billing/types'; export const metadata: Metadata = { title: { - template: "%s | Dashboard | CreemKit", - default: "Dashboard | CreemKit", + template: '%s | Dashboard | CreemKit', + default: 'Dashboard | CreemKit', }, - description: "Private dashboard for managing subscriptions, credits, and product data.", + description: 'Private dashboard for managing subscriptions, credits, and product data.', robots: { index: false, follow: false, }, }; -export default function DashboardLayout({ - children, -}: { - children: React.ReactNode; -}) { - return <>{children}; +export default async function DashboardLayout({ children }: { children: React.ReactNode }) { + const [user, subscription] = await Promise.all([getUser(), getUserSubscription()]); + + if (!user) redirect('/login'); + const isActiveSub = + subscription?.status === 'active' || subscription?.status === 'trialing'; + const planName = planNameFromId(isActiveSub ? (subscription?.planId ?? null) : null) as + | 'Free' + | 'Starter' + | 'Pro' + | 'Business'; + + return ( + + + + {children} + + + ); } diff --git a/src/app/(legal)/layout.tsx b/src/app/(legal)/layout.tsx index 6c9566d..0652bae 100644 --- a/src/app/(legal)/layout.tsx +++ b/src/app/(legal)/layout.tsx @@ -1,23 +1,30 @@ -import type { Metadata } from 'next' -import { Header } from '@/components/header' -import { FooterSection } from '@/features/landing/components/footer-section' +import type { Metadata } from "next"; +import { Header } from "@/components/header"; +import { FooterSection } from "@/features/landing/components/footer-section"; export const metadata: Metadata = { title: { - template: '%s | Legal | CreemKit', - default: 'Legal | CreemKit', + template: "%s | CreemKit", + default: "Legal | CreemKit", }, - description: 'Legal documents for CreemKit, including privacy policy and terms of service.', -} + description: + "Legal documents for CreemKit, including privacy policy and terms of service.", +}; -export default function LegalLayout({ children }: { children: React.ReactNode }) { +export default function LegalLayout({ + children, +}: { + children: React.ReactNode; +}) { return ( <>
-
{children}
+
+ {children} +
- ) + ); } diff --git a/src/app/(legal)/privacy/page.mdx b/src/app/(legal)/privacy/page.mdx index cc41ca4..6083031 100644 --- a/src/app/(legal)/privacy/page.mdx +++ b/src/app/(legal)/privacy/page.mdx @@ -1,8 +1,8 @@ {/* src/app/(legal)/privacy/page.mdx */} export const metadata = { - title: "Privacy Policy", - description: "How CreemKit collects, uses, and protects your data.", + title: 'Privacy Policy', + description: 'How CreemKit collects, uses, and protects your data.', }; # Privacy Policy @@ -175,4 +175,4 @@ We encourage you to review this policy periodically. If you have any questions about this Privacy Policy, you can reach us at: -- **Email:** support@creemkit.com \ No newline at end of file +- **Email:** support@creemkit.com diff --git a/src/app/(legal)/terms/page.mdx b/src/app/(legal)/terms/page.mdx index f77abd4..beed413 100644 --- a/src/app/(legal)/terms/page.mdx +++ b/src/app/(legal)/terms/page.mdx @@ -1,8 +1,8 @@ {/* src/app/(legal)/terms/page.mdx */} export const metadata = { - title: "Terms of Service", - description: "Terms and conditions for using CreemKit.", + title: 'Terms of Service', + description: 'Terms and conditions for using CreemKit.', }; # Terms of Service @@ -192,4 +192,4 @@ Continued use of the Service after changes take effect constitutes your acceptan If you have any questions about these Terms, you can reach us at: -- **Email:** support@creemkit.com \ No newline at end of file +- **Email:** support@creemkit.com diff --git a/src/app/(marketing)/layout.tsx b/src/app/(marketing)/layout.tsx index f6b2cf6..838477d 100644 --- a/src/app/(marketing)/layout.tsx +++ b/src/app/(marketing)/layout.tsx @@ -1,7 +1,8 @@ -import type { Metadata } from 'next' -import { Header } from '@/components/header' -import { CallToAction } from '@/features/landing/components/call-to-action' -import { FooterSection } from '@/features/landing/components/footer-section' +import type { Metadata } from 'next'; +import { ViewTransition } from 'react'; +import { Header } from '@/components/header'; +import { CallToAction } from '@/features/landing/components/call-to-action'; +import { FooterSection } from '@/features/landing/components/footer-section'; export const metadata: Metadata = { title: { @@ -10,15 +11,15 @@ export const metadata: Metadata = { }, description: 'Production-ready Next.js starter with Supabase auth and Creem payments pre-integrated.', -} +}; export default function MarketingLayout({ children }: { children: React.ReactNode }) { return ( <>
- {children} + {children} - ) + ); } diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index a6811e3..76f131e 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -1,12 +1,11 @@ -import type { Metadata } from 'next' -import FeaturesSection from '@/features/landing/components/features-section' -import HeroSection from '@/features/landing/components/hero-section' +import type { Metadata } from 'next'; +import FeaturesSection from '@/features/landing/components/features-section'; +import HeroSection from '@/features/landing/components/hero-section'; export const metadata: Metadata = { title: 'Next.js + Supabase + Creem Starter', - description: - 'Launch your SaaS faster with prebuilt auth, billing, subscriptions, and credits.', -} + description: 'Launch your SaaS faster with prebuilt auth, billing, subscriptions, and credits.', +}; export default function Page() { return ( @@ -14,5 +13,5 @@ export default function Page() { - ) + ); } diff --git a/src/app/(marketing)/pricing/page.tsx b/src/app/(marketing)/pricing/page.tsx index cb33f53..541dc01 100644 --- a/src/app/(marketing)/pricing/page.tsx +++ b/src/app/(marketing)/pricing/page.tsx @@ -1,17 +1,32 @@ -import type { Metadata } from 'next' -import { PricingSection } from '@/features/billing/components/pricing-section' -import { FaqsSection } from '@/features/billing/components/faqs-section' +import type { Metadata } from 'next'; +import { getUserSubscription } from '@/features/billing/actions/subscription'; +import { PricingSection } from '@/features/billing/components/pricing-section'; +import { FaqsSection } from '@/features/billing/components/faqs-section'; +import { UrlToast } from '@/components/url-toast'; +import { PRICING_ERRORS } from '@/features/billing/errors'; +import { resolveError } from '@/lib/errors'; export const metadata: Metadata = { title: 'Pricing', description: 'Simple plans for side projects, growing SaaS products, and teams.', -} +}; + +export default async function PricingPage({ + searchParams, +}: { + searchParams: Promise<{ error?: string }>; +}) { + const [{ error }, subscription] = await Promise.all([searchParams, getUserSubscription()]); + const isActiveSub = + subscription?.status === 'active' || subscription?.status === 'trialing'; + const currentPlanId = isActiveSub ? (subscription?.planId ?? null) : null; + const errorMessage = resolveError(error, PRICING_ERRORS); -export default function PricingPage() { return ( <> - + {errorMessage && } + - ) + ); } diff --git a/src/app/api/webhooks/creem/route.ts b/src/app/api/webhooks/creem/route.ts new file mode 100644 index 0000000..3301655 --- /dev/null +++ b/src/app/api/webhooks/creem/route.ts @@ -0,0 +1 @@ +export { handleCreemWebhook as POST } from '@/features/billing/webhooks/handler'; diff --git a/src/app/globals.css b/src/app/globals.css index 68422d6..a252f44 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -129,8 +129,29 @@ } } - html, body { scroll-behavior: smooth; -} \ No newline at end of file +} + +/* View transitions: suppress all root animations by default (cross-layout navigations are instant) */ +::view-transition-old(root), +::view-transition-new(root), +::view-transition-old(site-header), +::view-transition-new(site-header) { + animation: none; +} + +/* Enable cross-fade only for same-layout (marketing ↔ marketing) navigations */ +@keyframes vt-fade-in { + from { opacity: 0; } +} +@keyframes vt-fade-out { + to { opacity: 0; } +} +html:active-view-transition-type(same-layout) ::view-transition-old(root) { + animation: 200ms ease both vt-fade-out; +} +html:active-view-transition-type(same-layout) ::view-transition-new(root) { + animation: 200ms ease both vt-fade-in; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ea18b3b..b4d5e75 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,25 +1,26 @@ -import type { Metadata } from 'next' -import { Inter } from 'next/font/google' +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; -import './globals.css' -import { ThemeProvider } from '@/components/theme-provider' -import { cn } from '@/lib/utils' -import Script from 'next/script' +import './globals.css'; +import { ThemeProvider } from '@/components/theme-provider'; +import { cn } from '@/lib/utils'; +import Script from 'next/script'; +import { SonnerToaster } from '../components/sonner-toaster'; -const inter = Inter({ subsets: ['latin'], variable: '--font-sans' }) +const inter = Inter({ subsets: ['latin'], variable: '--font-sans' }); -const siteName = 'CreemKit' +const siteName = 'CreemKit'; const siteDescription = - 'Production-ready Next.js starter with Supabase auth and Creem payments pre-integrated.' + 'Production-ready Next.js starter with Supabase auth and Creem payments pre-integrated.'; function getMetadataBase() { - const fallbackUrl = 'http://localhost:3000' - const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? fallbackUrl + const fallbackUrl = 'http://localhost:3000'; + const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? fallbackUrl; try { - return new URL(appUrl) + return new URL(appUrl); } catch { - return new URL(fallbackUrl) + return new URL(fallbackUrl); } } @@ -46,18 +47,19 @@ export const metadata: Metadata = { title: siteName, description: siteDescription, }, -} +}; export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode + children: React.ReactNode; }>) { return ( {process.env.NODE_ENV === 'development' && ( @@ -69,8 +71,11 @@ export default function RootLayout({ strategy="afterInteractive" /> )} - {children} + + {children} + + - ) + ); } diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index c003f44..7a09ea5 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,5 +1,5 @@ -import Link from 'next/link' -import { Button } from '@/components/ui/button' +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; export default function NotFound() { return ( @@ -13,5 +13,5 @@ export default function NotFound() { Back to home
- ) + ); } diff --git a/src/components/header.tsx b/src/components/header.tsx index 3d72bc4..f112044 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,15 +1,16 @@ -"use client"; -import Link from "next/link"; -import { Logo } from "@/components/logo"; -import { IconMenu2, IconX } from "@tabler/icons-react"; -import { Button } from "@/components/ui/button"; -import React from "react"; -import { cn } from "@/lib/utils"; -import { AnimatePresence, motion } from "motion/react"; +'use client'; +import Link from 'next/link'; +import { Logo } from '@/components/logo'; +import { IconMenu2, IconX } from '@tabler/icons-react'; +import { Button } from '@/components/ui/button'; +import React from 'react'; +import { cn } from '@/lib/utils'; +import { AnimatePresence, motion } from 'motion/react'; +import { useUser } from '@/features/auth/hooks/use-user'; const menuItems = [ - { name: "Features", href: "/#features" }, - { name: "Pricing", href: "/pricing" }, + { name: 'Features', href: '/#features' }, + { name: 'Pricing', href: '/pricing' }, ]; export const navLinks = menuItems.map((item) => ({ @@ -19,6 +20,7 @@ export const navLinks = menuItems.map((item) => ({ export const Header = () => { const [menuState, setMenuState] = React.useState(false); + const { user, loading } = useUser(); const close = () => setMenuState(false); @@ -34,23 +36,17 @@ export const Header = () => { exit={{ opacity: 0 }} transition={{ duration: 0.2 }} onClick={close} - className="fixed inset-0 z-10 backdrop-blur-sm bg-background/20 lg:hidden" + className="fixed inset-0 z-10 bg-background/20 backdrop-blur-sm lg:hidden" /> )} -