diff --git a/sdk-showcase/.env.local.example b/sdk-showcase/.env.local.example new file mode 100644 index 00000000..05a06067 --- /dev/null +++ b/sdk-showcase/.env.local.example @@ -0,0 +1,3 @@ +# Terminal49 API Token +# Get your API key from https://app.terminal49.com/settings/api +T49_API_TOKEN=your_api_token_here diff --git a/sdk-showcase/.gitignore b/sdk-showcase/.gitignore new file mode 100644 index 00000000..52f9a3ac --- /dev/null +++ b/sdk-showcase/.gitignore @@ -0,0 +1,34 @@ +# Dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# Testing +/coverage + +# Next.js +/.next/ +/out/ + +# Production +/build + +# Misc +.DS_Store +*.pem + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env files +.env*.local + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts diff --git a/sdk-showcase/PLAN.md b/sdk-showcase/PLAN.md new file mode 100644 index 00000000..c13d355d --- /dev/null +++ b/sdk-showcase/PLAN.md @@ -0,0 +1,610 @@ +# Terminal49 SDK Showcase Application Plan + +## Overview + +Build a comprehensive Next.js application that showcases all Terminal49 TypeScript SDK capabilities while serving as a developer reference and demonstrating business use cases for container tracking. + +**Goals:** +1. Showcase all SDK methods with working examples +2. Provide developer reference with code snippets for each operation +3. Demonstrate real business workflows (tracking, monitoring, demurrage management) +4. Identify SDK improvement opportunities and documentation gaps + +--- + +## Technology Stack + +- **Framework:** Next.js 14+ (App Router) +- **Language:** TypeScript +- **UI Library:** Kumo (Cloudflare) - semantic tokens required +- **SDK:** `@terminal49/sdk` +- **State:** React Server Components + TanStack Query for client-side caching +- **Code Display:** Shiki (syntax highlighting) + +--- + +## Application Structure + +``` +sdk-showcase/ # Location: root of this repo +├── .env.local.example # T49_API_TOKEN template +├── app/ +│ ├── layout.tsx # Root layout with Kumo theme +│ ├── page.tsx # Dashboard +│ ├── tracking-requests/ +│ │ ├── page.tsx # List tracking requests +│ │ ├── new/page.tsx # Create (with infer flow) +│ │ └── [id]/page.tsx # Detail view +│ ├── shipments/ +│ │ ├── page.tsx # List shipments +│ │ └── [id]/page.tsx # Detail (stop/resume tracking) +│ ├── containers/ +│ │ ├── page.tsx # List containers +│ │ └── [id]/ +│ │ ├── page.tsx # Detail view +│ │ ├── events/page.tsx # Transport events timeline +│ │ ├── route/page.tsx # Route visualization +│ │ └── demurrage/page.tsx # LFD, fees, holds +│ ├── shipping-lines/page.tsx # Carrier browser +│ ├── search/page.tsx # Global search +│ └── webhooks/page.tsx # Webhook event simulator +├── components/ +│ ├── layout/ # Sidebar, PageHeader +│ ├── features/ # Domain components +│ └── code-panel/ # SDK code display +├── lib/ +│ ├── terminal49/client.ts # SDK client singleton +│ └── hooks/ # Custom hooks +└── types/ # TypeScript types +``` + +--- + +## SDK Method Coverage by Page + +### Dashboard (`/`) +- `client.shipments.list()` - Summary counts +- `client.containers.list({ status: 'available' })` - Available containers +- `client.trackingRequests.list()` - Pending requests +- `client.trackContainer()` - Quick track action + +### Tracking Requests + +**List (`/tracking-requests`)** +- `client.trackingRequests.list(filters, { page, pageSize })` + +**Create (`/tracking-requests/new`)** +- `client.trackingRequests.inferNumber(number)` - Auto-detect +- `client.trackingRequests.createFromInfer(number, options)` - Smart create +- `client.trackingRequests.create(params)` - Explicit create + +**Detail (`/tracking-requests/[id]`)** +- `client.trackingRequests.get(id)` +- `client.trackingRequests.update(id, attrs)` + +### Shipments + +**List (`/shipments`)** +- `client.shipments.list({ status, port, carrier, updatedAfter })` + +**Detail (`/shipments/[id]`)** +- `client.shipments.get(id, includeContainers)` +- `client.shipments.update(id, attrs)` - Edit ref numbers/tags +- `client.shipments.stopTracking(id)` +- `client.shipments.resumeTracking(id)` + +### Containers + +**List (`/containers`)** +- `client.containers.list({ status, port, carrier })` + +**Detail (`/containers/[id]`)** +- `client.containers.get(id, ['shipment', 'pod_terminal'])` +- `client.containers.refresh(id)` - Manual refresh + +**Events (`/containers/[id]/events`)** +- `client.containers.events(id)` - Transport events +- `client.containers.rawEvents(id)` - Raw unprocessed events + +**Route (`/containers/[id]/route`)** +- `client.containers.route(id)` - Route with vessels/ports + +**Demurrage (`/containers/[id]/demurrage`)** +- `client.getDemurrage(containerId)` - LFD, fees, holds +- `client.getRailMilestones(containerId)` - Rail tracking (North America) + +### Shipping Lines (`/shipping-lines`) +- `client.shippingLines.list(search)` - Search carriers by name/SCAC + +### Search (`/search`) +- `client.search(query)` - Global search + +### Utilities (used across app) +- `client.deserialize(document)` - JSON:API to plain objects + +--- + +## Key Features by Page + +### 1. Dashboard +- Summary cards: shipments, containers, tracking requests count +- Status distribution (on_ship, available, not_available, etc.) +- LFD alerts: containers within 3 days of Last Free Day +- Quick actions: New tracking, Search +- Recent activity feed + +### 2. Create Tracking Request (Infer Flow) +Multi-step wizard demonstrating the intelligent tracking creation: + +1. **Enter Number** - User inputs tracking number +2. **Auto-Detect** - Show `inferNumber` results: number type, carrier candidates +3. **Confirm/Select** - If `decision === 'needs_confirmation'`, show carrier dropdown +4. **Submit** - Call `createFromInfer` or `create` +5. **Result** - Show success/failure, link to shipment + +### 3. Container Detail Tabs +- **Overview** - Number, equipment, status, terminal +- **Events Timeline** - Visual timeline with event icons +- **Route** - Map + legs table (origin → destination) +- **Demurrage** - LFD countdown, fees, holds, availability + +### 4. Code Panel Component +Every page shows collapsible SDK code: +```tsx + +``` + +### 5. Webhook Simulator +Mock webhook events without real webhook setup: +- Event type selector +- Example payload display +- Simulated event handling code + +--- + +## Container Status Values (for badges) + +| Status | Color | Description | +|--------|-------|-------------| +| `new` | gray | Tracking started, status unknown | +| `on_ship` | blue | In transit by vessel | +| `available` | green | Ready for pickup | +| `not_available` | yellow | At port but restricted | +| `grounded` | orange | Availability unknown | +| `awaiting_inland_transfer` | purple | Waiting for rail | +| `on_rail` | indigo | In transit by rail | +| `picked_up` | teal | Out for delivery | +| `off_dock` | cyan | At alternative facility | +| `delivered` | emerald | Delivery confirmed | +| `empty_returned` | gray | Container returned | + +--- + +## Business Use Cases to Document + +### 1. First-Time Container Tracking +**Persona:** Integration engineer +**Flow:** Enter number → Infer → Confirm carrier → Track → View shipment + +### 2. Monitor Active Shipments +**Persona:** Logistics operator +**Flow:** Filter in-transit → Sort by ETA → View events → Check route + +### 3. Demurrage Management +**Persona:** Freight forwarder +**Flow:** Dashboard LFD alerts → Container demurrage page → Check holds/fees + +### 4. Tracking Lifecycle Management +**Persona:** BCO team lead +**Flow:** View shipment → Stop tracking → Later: Resume tracking + +--- + +## SDK Improvement Opportunities + +### Missing SDK Methods +| Resource | Gap | +|----------|-----| +| Webhooks | No CRUD for webhooks (create, list, update, delete) | +| Parties | No SDK methods for parties resource | +| Vessels | No vessel lookup methods | +| Terminals | No terminal lookup methods | +| Ports | No port lookup methods | + +### Type Improvements +- Explicit enum types for `current_status` values +- Explicit enum types for transport event types +- Better generics for `format` option variations +- Typed filter parameters for each resource + +### Documentation Gaps +- More examples of `raw` vs `mapped` vs `both` response formats +- Pagination best practices (iterating all pages) +- Valid `include` values for each resource +- Error handling scenarios + +--- + +## Implementation Phases + +### Phase 1: Foundation +- [ ] Create Next.js 14 project at `sdk-showcase/` with TypeScript +- [ ] Install `@terminal49/sdk` (local path reference) +- [ ] Install and configure Kumo UI (`@cloudflare/kumo`) +- [ ] Set up SDK client singleton with env-based token +- [ ] Create layout (sidebar, header) with Kumo components +- [ ] Build Dashboard with summary metrics +- [ ] Configure Vercel deployment settings + +### Phase 2: Core CRUD Pages +- [ ] Tracking Requests: list, create (with infer), detail +- [ ] Shipments: list, detail (with stop/resume) +- [ ] Containers: list, detail + +### Phase 3: Advanced Features +- [ ] Container events timeline +- [ ] Container route visualization +- [ ] Demurrage page with rail milestones +- [ ] Global search + +### Phase 4: Polish & Documentation +- [ ] Shipping lines browser +- [ ] Webhook simulator +- [ ] Code panel for all pages +- [ ] SDK improvement tracking notes + +--- + +## Critical Files Reference + +| File | Purpose | +|------|---------| +| `sdks/typescript-sdk/src/client.ts` | SDK implementation - all methods | +| `docs/sdk/methods.mdx` | Method signatures reference | +| `docs/api-docs/in-depth-guides/container-statuses.mdx` | Status values | +| `docs/api-docs/in-depth-guides/webhooks.mdx` | Webhook event types | +| `docs/api-docs/in-depth-guides/auto-detect-carrier.mdx` | Infer API flow | + +--- + +## Verification Plan + +1. **Build Check:** `pnpm build` succeeds without errors +2. **Type Check:** `pnpm tsc --noEmit` passes +3. **SDK Coverage:** Audit that all 24+ SDK methods are used somewhere +4. **Code Examples:** Ensure all code snippets are valid and runnable +5. **Business Flows:** Walk through each use case end-to-end with test token +6. **Vercel Preview:** Deploy preview and test all pages + +--- + +## Configuration Decisions + +- **Location:** Inside this repo at `sdk-showcase/` +- **Data Source:** Live Terminal49 API with test token (via `T49_API_TOKEN` env var) +- **Deployment:** Vercel with Next.js optimizations + +--- + +## Visual Design + +### Layout Structure + +``` +┌──────────────────────────────────────────────────────────────────────────────┐ +│ Terminal49 SDK Showcase [Search] [Theme] │ +├────────────────┬─────────────────────────────────────────────────────────────┤ +│ │ │ +│ SIDEBAR │ PAGE CONTENT │ +│ ─────────── │ ───────────────────────────────────────────────────────── │ +│ │ │ +│ Dashboard │ ┌─────────────────────────────────────────────────────┐ │ +│ │ │ Page Header [Actions ▼] │ │ +│ TRACKING │ │ Breadcrumbs > Current Page │ │ +│ ├ Requests │ └─────────────────────────────────────────────────────┘ │ +│ └ + New │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ SHIPMENTS │ │ Card 1 │ │ Card 2 │ │ Card 3 │ │ Card 4 │ │ +│ └ All │ │ Metric │ │ Metric │ │ Metric │ │ Metric │ │ +│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ CONTAINERS │ │ +│ └ All │ ┌─────────────────────────────────────────────────────┐ │ +│ │ │ Main Content Area │ │ +│ REFERENCE │ │ (Tables, Forms, Timelines, etc.) │ │ +│ ├ Carriers │ │ │ │ +│ └ Webhooks │ │ │ │ +│ │ └─────────────────────────────────────────────────────┘ │ +│ │ │ +│ │ ┌─────────────────────────────────────────────────────┐ │ +│ │ │ ▼ SDK Code │ │ +│ │ │ ┌───────────────────────────────────────────────┐ │ │ +│ │ │ │ const shipments = await client.shipments... │ │ │ +│ │ │ └───────────────────────────────────────────────┘ │ │ +│ │ └─────────────────────────────────────────────────────┘ │ +│ │ │ +└────────────────┴─────────────────────────────────────────────────────────────┘ +``` + +### Dashboard Page + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Dashboard [+ Track Container] │ +│ Overview of your tracked shipments and containers │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ +│ │ 📦 142 │ │ 🚢 89 │ │ ✅ 23 │ │ ⚠️ 5 │ +│ │ Total │ │ In Transit │ │ Available │ │ LFD < 3 days │ +│ │ Containers │ │ Shipments │ │ for Pickup │ │ Urgent │ +│ └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘ +│ │ +│ ┌────────────────────────────────────┐ ┌──────────────────────────────────┐ +│ │ Status Distribution │ │ LFD Alerts │ +│ │ ════════════════════════════════ │ │ ════════════════════════════════│ +│ │ ████████████░░░░ on_ship (45) │ │ MSCU1234567 LFD: Tomorrow │ +│ │ ████████░░░░░░░░ available (23) │ │ KOCU9876543 LFD: 2 days │ +│ │ ███░░░░░░░░░░░░░ not_avail (8) │ │ MSKU5555555 LFD: 3 days │ +│ │ ██░░░░░░░░░░░░░░ on_rail (5) │ │ [View All →] │ +│ └────────────────────────────────────┘ └──────────────────────────────────┘ +│ │ +│ ▼ SDK Code │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ // Fetch dashboard data │ +│ │ const [shipments, containers] = await Promise.all([ │ +│ │ client.shipments.list({ status: 'in_transit' }, { format: 'mapped' }),│ +│ │ client.containers.list({ status: 'available' }, { format: 'mapped' }) │ +│ │ ]); │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Tracking Request Creation (Infer Flow) + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Create Tracking Request │ +│ Track a container, booking, or bill of lading │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Step 1 Step 2 Step 3 Step 4 │ +│ │ ●───────────────○───────────────○───────────────○ │ +│ │ Enter Number Auto-Detect Select Carrier Confirm │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ │ +│ │ Enter Tracking Number │ +│ │ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ │ MSCU1234567 │ │ +│ │ └─────────────────────────────────────────────────────────────────┘ │ +│ │ Container number, booking number, or bill of lading │ +│ │ │ +│ │ [Detect & Continue →] │ +│ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ▼ SDK Code │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ // Step 1: Infer the number type and carrier │ +│ │ const inference = await client.trackingRequests.inferNumber('MSCU...');│ +│ │ │ +│ │ // Step 2: Create based on inference │ +│ │ const result = await client.trackingRequests.createFromInfer('MSCU...', │ +│ │ { scac: inference.shipping_line.selected?.scac } │ +│ │ ); │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Containers List Page + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Containers [Filter ▼] [Refresh] │ +│ All tracked containers │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Filters: Status [All ▼] Port [All ▼] Carrier [All ▼] │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Number │ Shipment │ Status │ LFD │ Terminal │ +│ ├─────────────────────────────────────────────────────────────────────────┤ +│ │ MSCU1234567 │ BOL-98765 │ ●available │ Feb 10 │ APM LA │ +│ │ KOCU9876543 │ BOL-45678 │ ●on_ship │ — │ — │ +│ │ MSKU5555555 │ BOL-11111 │ ●not_avail │ Feb 12 │ LBCT │ +│ │ OOLU3333333 │ BOL-22222 │ ●on_rail │ Feb 15 │ BNSF Chicago │ +│ │ CMAU7777777 │ BOL-33333 │ ●grounded │ — │ Pier 400 │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ◄ Previous Page 1 of 6 Next ► │ +│ │ +│ ▼ SDK Code │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ const containers = await client.containers.list( │ +│ │ { status: 'available', port: 'USLAX' }, │ +│ │ { page: 1, pageSize: 25, format: 'mapped' } │ +│ │ ); │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Container Detail with Tabs + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ← Back to Containers │ +│ Container MSCU1234567 [Refresh] [View Raw] │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Equipment: 40' High Cube Dry Status: ●available Seal: ABC123 │ +│ │ Shipment: BOL-98765 Terminal: APM Los Angeles │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ [Overview] [Events] [Route] [Demurrage] │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ EVENTS TIMELINE │ +│ ───────────────────────────────────────────────────────────────────────── │ +│ ●────●────●────●────●────○────○ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Feb 8, 2025 14:32 UTC │ +│ │ ● vessel_discharged 📍 Los Angeles, US │ +│ │ Container discharged from vessel at POD │ +│ │ Source: terminal │ +│ ├─────────────────────────────────────────────────────────────────────────┤ +│ │ Feb 5, 2025 08:15 UTC │ +│ │ ● vessel_arrived 📍 Los Angeles, US │ +│ │ Vessel arrived at port of discharge │ +│ │ Source: shipping_line │ +│ ├─────────────────────────────────────────────────────────────────────────┤ +│ │ Jan 25, 2025 16:00 UTC │ +│ │ ● vessel_departed 📍 Shanghai, CN │ +│ │ Vessel departed from port of lading │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ▼ SDK Code │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ const events = await client.containers.events(containerId, { │ +│ │ format: 'mapped' │ +│ │ }); │ +│ │ // Returns: Array of transport events with timestamps and locations │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Shipment Detail with Stop/Resume + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ← Back to Shipments │ +│ Shipment BOL-98765432 [Stop Tracking] [Edit] [Raw] │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Carrier: MAERSK (MAEU) Bill of Lading: MAEUSEA12345678 │ +│ │ Vessel: MUNICH MAERSK Voyage: 501E │ +│ │ Customer: Acme Imports Inc. Tracking: ●Active │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ROUTE │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ │ +│ │ 🏭 Shanghai, CN 🏭 Los Angeles, US │ +│ │ Port of Lading Port of Discharge │ +│ │ ETD: Jan 25 ATD: Jan 25 ETA: Feb 5 ATA: Feb 5 │ +│ │ │ +│ │ ═══════════🚢═══════════► │ +│ │ MUNICH MAERSK / 501E │ +│ │ │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ CONTAINERS (3) │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Number │ Equipment │ Status │ LFD │ +│ │ MSCU1234567 │ 40' HC Dry │ ●available │ Feb 10 │ +│ │ MSCU1234568 │ 40' HC Dry │ ●available │ Feb 10 │ +│ │ MSCU1234569 │ 40' HC Reefer │ ●not_avail │ Feb 12 │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ▼ SDK Code │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ // Get shipment with containers │ +│ │ const shipment = await client.shipments.get(id, true, { │ +│ │ format: 'mapped' │ +│ │ }); │ +│ │ │ +│ │ // Stop tracking │ +│ │ await client.shipments.stopTracking(id); │ +│ │ │ +│ │ // Resume tracking │ +│ │ await client.shipments.resumeTracking(id); │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Demurrage Page + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Container MSCU1234567 › Demurrage │ +│ Last Free Day and terminal fees │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────────────────────────┐ ┌──────────────────────────────────┐ +│ │ LAST FREE DAY │ │ AVAILABILITY │ +│ │ ══════════════════════════════════│ │ ════════════════════════════════│ +│ │ │ │ │ +│ │ 📅 February 10, 2025 │ │ ✅ Available for Pickup │ +│ │ │ │ │ +│ │ ⏰ 2 days remaining │ │ Discharged: Feb 8, 14:32 UTC │ +│ │ [████████████░░░░░] │ │ Available since: Feb 8, 18:00 │ +│ │ │ │ │ +│ └────────────────────────────────────┘ └──────────────────────────────────┘ +│ │ +│ ┌────────────────────────────────────┐ ┌──────────────────────────────────┐ +│ │ FEES AT TERMINAL │ │ HOLDS AT TERMINAL │ +│ │ ══════════════════════════════════│ │ ════════════════════════════════│ +│ │ │ │ │ +│ │ ✅ No fees currently │ │ ✅ No holds │ +│ │ │ │ │ +│ │ (Fees will appear after LFD) │ │ Container is clear for pickup │ +│ │ │ │ │ +│ └────────────────────────────────────┘ └──────────────────────────────────┘ +│ │ +│ RAIL MILESTONES (if applicable) │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ Rail Carrier: BNSF (BNSF) │ +│ │ POD Rail Loaded: — │ +│ │ POD Rail Departed: — │ +│ │ IND Rail Arrived: — │ +│ │ IND Rail Unloaded: — │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +│ ▼ SDK Code │ +│ ┌─────────────────────────────────────────────────────────────────────────┐ +│ │ // Get demurrage information │ +│ │ const demurrage = await client.getDemurrage(containerId); │ +│ │ // { pickup_lfd, available_for_pickup, fees_at_pod_terminal, ... } │ +│ │ │ +│ │ // Get rail milestones (North America) │ +│ │ const rail = await client.getRailMilestones(containerId); │ +│ │ // { pod_rail_loaded_at, ind_rail_arrived_at, rail_events, ... } │ +│ └─────────────────────────────────────────────────────────────────────────┘ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### Color Tokens (Kumo) + +``` +SURFACES TEXT STATUS BADGES +───────────────────────────────────────────────────────────────────────────── +bg-kumo-base Page background text-kumo-default Primary ● new gray +bg-kumo-elevated Cards, modals text-kumo-secondary Secondary ● on_ship blue +bg-kumo-recessed Input fields text-kumo-muted Subtle ● available green + ● not_avail yellow +BORDERS INTERACTIVE ● grounded orange +───────────────────────────────────────────────────────────────────────────── +border-kumo-line Default border text-kumo-link Links ● on_rail indigo +border-kumo-focus Focus rings bg-kumo-accent Buttons ● picked_up teal +``` diff --git a/sdk-showcase/README.md b/sdk-showcase/README.md new file mode 100644 index 00000000..ff1e4797 --- /dev/null +++ b/sdk-showcase/README.md @@ -0,0 +1,164 @@ +# Terminal49 SDK Showcase + +A comprehensive Next.js application showcasing all Terminal49 TypeScript SDK capabilities. This app serves as both a developer reference and a demonstration of real business workflows for container tracking. + +## Features + +### Dashboard +- Summary metrics for shipments, containers, and tracking requests +- Status distribution visualization +- LFD alerts for containers approaching Last Free Day +- Quick track action + +### Tracking Requests +- **List**: View and filter all tracking requests with pagination +- **Create**: Multi-step wizard with intelligent carrier auto-detection (infer flow) +- **Detail**: View status, update, and manage individual requests + +### Shipments +- **List**: Filter by status, port, carrier with sorting +- **Detail**: Full shipment info with route visualization +- **Actions**: Stop/resume tracking, edit reference numbers and tags + +### Containers +- **List**: Filter and sort containers by status, terminal, carrier +- **Detail**: Comprehensive container info with terminal details +- **Events**: Transport events timeline with raw carrier events +- **Route**: Route visualization with legs, vessels, and timing +- **Demurrage**: LFD tracking, terminal fees, holds, and rail milestones + +### Reference +- **Shipping Lines**: Browse and search carriers by name or SCAC code +- **Webhooks**: Event simulator with payload examples and handler code + +### Global Search +- Search across containers, shipments, and tracking requests + +## SDK Methods Demonstrated + +| Method | Page | +|--------|------| +| `client.shipments.list()` | Dashboard, Shipments | +| `client.shipments.get()` | Shipment Detail | +| `client.shipments.update()` | Shipment Detail | +| `client.shipments.stopTracking()` | Shipment Detail | +| `client.shipments.resumeTracking()` | Shipment Detail | +| `client.containers.list()` | Dashboard, Containers | +| `client.containers.get()` | Container Detail | +| `client.containers.refresh()` | Container Detail | +| `client.containers.events()` | Container Events | +| `client.containers.rawEvents()` | Container Events | +| `client.containers.route()` | Container Route | +| `client.trackingRequests.list()` | Dashboard, Tracking Requests | +| `client.trackingRequests.get()` | Tracking Request Detail | +| `client.trackingRequests.create()` | Create Tracking | +| `client.trackingRequests.inferNumber()` | Create Tracking | +| `client.trackingRequests.createFromInfer()` | Create Tracking | +| `client.shippingLines.list()` | Shipping Lines | +| `client.search()` | Search | +| `client.getDemurrage()` | Container Demurrage | +| `client.getRailMilestones()` | Container Demurrage | + +## Getting Started + +### Prerequisites +- Node.js 18+ +- pnpm +- Terminal49 API token + +### Installation + +```bash +cd sdk-showcase +pnpm install +``` + +### Configuration + +Create a `.env.local` file: + +```env +T49_API_TOKEN=your_api_token_here +``` + +### Development + +```bash +pnpm dev +``` + +Open [http://localhost:3000](http://localhost:3000). + +### Production Build + +```bash +pnpm build +pnpm start +``` + +## Project Structure + +``` +sdk-showcase/ +├── src/ +│ ├── app/ # Next.js App Router pages +│ │ ├── page.tsx # Dashboard +│ │ ├── tracking-requests/ # Tracking request pages +│ │ ├── shipments/ # Shipment pages +│ │ ├── containers/ # Container pages with subpages +│ │ ├── shipping-lines/ # Carrier browser +│ │ ├── search/ # Global search +│ │ ├── webhooks/ # Webhook simulator +│ │ └── api/ # API routes for client actions +│ ├── components/ +│ │ ├── layout/ # Sidebar, PageHeader +│ │ ├── features/ # Card, Button, Badge components +│ │ └── code-panel/ # SDK code display component +│ └── lib/ +│ ├── terminal49/ # SDK client singleton +│ └── utils.ts # Helper functions +├── SDK_IMPROVEMENTS.md # SDK improvement tracking +└── README.md +``` + +## Technology Stack + +- **Framework**: Next.js 14 (App Router) +- **Language**: TypeScript +- **UI**: Kumo (Cloudflare) semantic tokens +- **SDK**: `@terminal49/sdk` +- **Data**: React Server Components for server-side data fetching + +## Container Status Values + +| Status | Description | +|--------|-------------| +| `new` | Tracking started, status unknown | +| `on_ship` | In transit by vessel | +| `available` | Ready for pickup | +| `not_available` | At port but restricted | +| `grounded` | Availability unknown | +| `awaiting_inland_transfer` | Waiting for rail | +| `on_rail` | In transit by rail | +| `picked_up` | Out for delivery | +| `off_dock` | At alternative facility | +| `delivered` | Delivery confirmed | +| `empty_returned` | Container returned | + +## Business Use Cases + +1. **First-Time Container Tracking**: Enter number → Auto-detect carrier → Confirm → Track → View shipment +2. **Monitor Active Shipments**: Filter in-transit → Sort by ETA → View events → Check route +3. **Demurrage Management**: Dashboard LFD alerts → Container demurrage page → Check holds/fees +4. **Tracking Lifecycle**: View shipment → Stop tracking → Later: Resume tracking + +## SDK Improvements + +See [SDK_IMPROVEMENTS.md](./SDK_IMPROVEMENTS.md) for documented SDK improvement opportunities including: +- Missing SDK methods (webhooks, vessels, terminals, ports) +- Type improvements (enum types, typed filters) +- Documentation gaps + +## License + +Internal use only. diff --git a/sdk-showcase/SDK_IMPROVEMENTS.md b/sdk-showcase/SDK_IMPROVEMENTS.md new file mode 100644 index 00000000..353eda63 --- /dev/null +++ b/sdk-showcase/SDK_IMPROVEMENTS.md @@ -0,0 +1,460 @@ +# Terminal49 SDK Improvement Opportunities + +This document tracks SDK improvement opportunities identified while building the SDK Showcase application. These observations can inform future SDK development. + +--- + +## Missing SDK Methods + +### Webhooks Resource +**Current State:** No SDK methods for webhook management. + +**Needed Methods:** +```typescript +// Create a webhook subscription +client.webhooks.create({ + url: 'https://example.com/webhook', + events: ['shipment.updated', 'container.updated'], + secret: 'webhook_secret' +}) + +// List webhook subscriptions +client.webhooks.list() + +// Get a specific webhook +client.webhooks.get(webhookId) + +// Update a webhook +client.webhooks.update(webhookId, { enabled: false }) + +// Delete a webhook +client.webhooks.delete(webhookId) + +// Test a webhook (trigger test event) +client.webhooks.test(webhookId) +``` + +**Use Case:** Developers building integrations need programmatic webhook management for CI/CD pipelines, multi-tenant setups, and self-service onboarding. + +--- + +### Parties Resource +**Current State:** No SDK methods for parties (consignees, shippers, notify parties). + +**Needed Methods:** +```typescript +// List parties associated with a shipment +client.parties.list({ shipmentId: 'ship_123' }) + +// Get party details +client.parties.get(partyId) +``` + +**Use Case:** Visibility platforms need to display consignee/shipper information and build relationship graphs. + +--- + +### Vessels Resource +**Current State:** No vessel lookup methods. + +**Needed Methods:** +```typescript +// Search vessels by name or IMO +client.vessels.search('EVER GIVEN') +client.vessels.get(vesselId) +client.vessels.getByIMO('9811000') + +// Get vessel current position/schedule (if available) +client.vessels.position(vesselId) +``` + +**Use Case:** Logistics operators want to track vessel schedules and understand delays. + +--- + +### Terminals Resource +**Current State:** No terminal lookup methods. + +**Needed Methods:** +```typescript +// Search terminals +client.terminals.search({ port: 'USLAX' }) +client.terminals.get(terminalId) + +// Get terminal operating hours, contacts (if available) +client.terminals.details(terminalId) +``` + +**Use Case:** Freight forwarders need terminal information for pickup coordination. + +--- + +### Ports Resource +**Current State:** No port lookup methods. + +**Needed Methods:** +```typescript +// Search ports by name or UN/LOCODE +client.ports.search('Los Angeles') +client.ports.getByLocode('USLAX') +``` + +**Use Case:** Build port selection dropdowns and validate port codes. + +--- + +## Type Improvements + +### Explicit Enum Types for Status Values + +**Current State:** Status values are typed as `string`. + +**Improvement:** Define explicit union types: + +```typescript +// Container status +type ContainerStatus = + | 'new' + | 'on_ship' + | 'available' + | 'not_available' + | 'grounded' + | 'awaiting_inland_transfer' + | 'on_rail' + | 'picked_up' + | 'off_dock' + | 'delivered' + | 'empty_returned'; + +// Tracking request status +type TrackingRequestStatus = + | 'pending' + | 'tracking' + | 'not_found' + | 'duplicate' + | 'expired'; + +// Shipment status +type ShipmentStatus = + | 'created' + | 'in_transit' + | 'arrived' + | 'delivered' + | 'tracking_stopped'; +``` + +**Benefit:** Better TypeScript autocomplete, compile-time validation, and documentation. + +--- + +### Transport Event Type Enums + +**Current State:** Event types are typed as `string`. + +**Improvement:** +```typescript +type TransportEventType = + | 'vessel_loaded' + | 'vessel_departed' + | 'vessel_arrived' + | 'vessel_discharged' + | 'gate_in' + | 'gate_out' + | 'empty_returned' + | 'rail_loaded' + | 'rail_departed' + | 'rail_arrived' + | 'rail_unloaded' + // ... etc. +``` + +--- + +### Typed Filter Parameters + +**Current State:** Filter parameters are loosely typed or use `Record`. + +**Improvement:** +```typescript +interface ContainerFilters { + status?: ContainerStatus | ContainerStatus[]; + port_of_discharge_locode?: string; + shipping_line_scac?: string; + updated_after?: string; // ISO date + pickup_lfd_before?: string; // ISO date +} + +// Usage with type safety +client.containers.list({ status: 'available', port_of_discharge_locode: 'USLAX' }) +``` + +--- + +### Include Parameter Types + +**Current State:** Include values are strings without validation. + +**Improvement:** +```typescript +// Define valid includes per resource +type ContainerIncludes = 'shipment' | 'pod_terminal' | 'transport_events'; +type ShipmentIncludes = 'containers' | 'tracking_request' | 'shipping_line'; + +// Type-safe usage +client.containers.get(id, ['shipment', 'pod_terminal'] satisfies ContainerIncludes[]) +``` + +--- + +## Documentation Gaps + +### Response Format Options + +**Gap:** Limited documentation on `format` option variations. + +**Needed Documentation:** +- When to use `raw` vs `mapped` vs `both` +- Memory/performance implications +- Example outputs for each format +- How `raw` preserves JSON:API structure + +**Example to add:** +```typescript +// Raw format - preserves JSON:API structure +const raw = await client.containers.get(id, [], { format: 'raw' }); +// Returns: { data: { id, type, attributes, relationships }, included: [...] } + +// Mapped format - plain JavaScript objects +const mapped = await client.containers.get(id, [], { format: 'mapped' }); +// Returns: { id, number, status, shipment: { id, ... } } + +// Both format - returns both versions +const both = await client.containers.get(id, [], { format: 'both' }); +// Returns: { raw: {...}, mapped: {...} } +``` + +--- + +### Pagination Best Practices + +**Gap:** No documentation on iterating through all pages. + +**Needed Documentation:** +```typescript +// Recommended pattern for paginating through all results +async function* getAllShipments(client: Terminal49Client) { + let page = 1; + const pageSize = 100; + + while (true) { + const result = await client.shipments.list({}, { page, pageSize, format: 'mapped' }); + const shipments = Array.isArray(result) ? result : result?.data || []; + + if (shipments.length === 0) break; + + for (const shipment of shipments) { + yield shipment; + } + + if (shipments.length < pageSize) break; + page++; + } +} + +// Usage +for await (const shipment of getAllShipments(client)) { + console.log(shipment.id); +} +``` + +--- + +### Valid Include Values Per Resource + +**Gap:** No documentation listing valid `include` values for each resource. + +**Needed Documentation:** + +| Resource | Valid Includes | +|----------|----------------| +| `containers.get()` | `shipment`, `pod_terminal`, `transport_events` | +| `containers.list()` | `shipment` | +| `shipments.get()` | `containers`, `tracking_request`, `shipping_line`, `port_of_lading`, `port_of_discharge` | +| `shipments.list()` | `containers`, `shipping_line` | +| `trackingRequests.get()` | `shipment`, `shipping_line` | +| `trackingRequests.list()` | `shipping_line` | + +--- + +### Error Handling Scenarios + +**Gap:** Limited documentation on error types and handling strategies. + +**Needed Documentation:** +```typescript +import { Terminal49Client, Terminal49Error } from '@terminal49/sdk'; + +try { + const container = await client.containers.get('invalid_id'); +} catch (error) { + if (error instanceof Terminal49Error) { + switch (error.status) { + case 401: + // Invalid or expired API token + console.error('Authentication failed:', error.message); + break; + case 404: + // Resource not found + console.error('Container not found'); + break; + case 422: + // Validation error (e.g., invalid filter value) + console.error('Validation error:', error.details); + break; + case 429: + // Rate limited + const retryAfter = error.headers?.['retry-after']; + console.error(`Rate limited. Retry after ${retryAfter}s`); + break; + default: + console.error('API error:', error.message); + } + } else { + // Network error or other issue + console.error('Request failed:', error); + } +} +``` + +--- + +## API Improvement Suggestions + +### Bulk Operations + +**Current State:** No bulk create/update operations. + +**Suggestion:** +```typescript +// Create multiple tracking requests at once +client.trackingRequests.createBulk([ + { request_number: 'MSCU1234567', request_type: 'container_number', scac: 'MSCU' }, + { request_number: 'KOCU9876543', request_type: 'container_number', scac: 'ONEY' }, + // ... up to 100 items +]) +``` + +**Use Case:** Onboarding scenarios where customers need to track many containers at once. + +--- + +### Streaming/SSE for Real-time Updates + +**Current State:** Polling is the only way to get updates. + +**Suggestion:** Server-Sent Events endpoint for real-time container updates: +```typescript +const eventSource = client.containers.subscribe(['container_123', 'container_456']); + +eventSource.on('status_change', (event) => { + console.log(`${event.containerId} changed to ${event.newStatus}`); +}); + +eventSource.on('eta_update', (event) => { + console.log(`${event.containerId} ETA updated to ${event.newEta}`); +}); +``` + +--- + +## SDK Implementation Notes + +### Response Format Normalization + +The SDK handles both snake_case (API) and camelCase (JavaScript convention) property names. When using `format: 'mapped'`, properties are available in both formats for compatibility: + +```typescript +// Both work: +container.shipping_line // snake_case (matches API) +container.shippingLine // camelCase (JavaScript convention) +``` + +**Recommendation:** Document this behavior explicitly and consider standardizing on camelCase for the mapped format. + +--- + +### Client Instance Management + +**Current Pattern in Showcase:** +```typescript +// lib/terminal49/client.ts +let clientInstance: Terminal49Client | null = null; + +export function getClient(): Terminal49Client { + if (!clientInstance) { + clientInstance = new Terminal49Client({ + apiToken: process.env.T49_API_TOKEN!, + }); + } + return clientInstance; +} +``` + +**Consideration:** Document recommended patterns for: +- Server-side singleton (Next.js API routes, Server Components) +- Client-side usage (if supported) +- Multi-tenant scenarios (token per request) + +--- + +## Priority Ranking + +Based on developer experience impact: + +1. **High Priority** + - Explicit enum types for status values + - Typed filter parameters + - Error handling documentation + +2. **Medium Priority** + - Webhook CRUD methods + - Pagination documentation + - Include values documentation + +3. **Lower Priority** + - Vessels/Terminals/Ports lookup methods + - Bulk operations + - Real-time streaming + +--- + +## Testing Coverage + +Methods tested in this showcase app: + +| Method | Page | Status | +|--------|------|--------| +| `client.shipments.list()` | Dashboard, Shipments | ✅ | +| `client.shipments.get()` | Shipment Detail | ✅ | +| `client.shipments.update()` | Shipment Detail | ✅ | +| `client.shipments.stopTracking()` | Shipment Detail | ✅ | +| `client.shipments.resumeTracking()` | Shipment Detail | ✅ | +| `client.containers.list()` | Dashboard, Containers | ✅ | +| `client.containers.get()` | Container Detail | ✅ | +| `client.containers.refresh()` | Container Detail | ✅ | +| `client.containers.events()` | Container Events | ✅ | +| `client.containers.rawEvents()` | Container Events | ✅ | +| `client.containers.route()` | Container Route | ✅ | +| `client.trackingRequests.list()` | Dashboard, Tracking Requests | ✅ | +| `client.trackingRequests.get()` | Tracking Request Detail | ✅ | +| `client.trackingRequests.create()` | Create Tracking | ✅ | +| `client.trackingRequests.inferNumber()` | Create Tracking | ✅ | +| `client.trackingRequests.createFromInfer()` | Create Tracking | ✅ | +| `client.shippingLines.list()` | Shipping Lines | ✅ | +| `client.search()` | Search | ✅ | +| `client.getDemurrage()` | Container Demurrage | ✅ | +| `client.getRailMilestones()` | Container Demurrage | ✅ | + +--- + +*Last updated: Generated during SDK Showcase development* diff --git a/sdk-showcase/next.config.mjs b/sdk-showcase/next.config.mjs new file mode 100644 index 00000000..b5977efb --- /dev/null +++ b/sdk-showcase/next.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + serverComponentsExternalPackages: ['@terminal49/sdk'], + }, +}; + +export default nextConfig; diff --git a/sdk-showcase/package.json b/sdk-showcase/package.json new file mode 100644 index 00000000..4172af45 --- /dev/null +++ b/sdk-showcase/package.json @@ -0,0 +1,31 @@ +{ + "name": "terminal49-sdk-showcase", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@tanstack/react-query": "^5.0.0", + "@terminal49/sdk": "link:../sdks/typescript-sdk", + "clsx": "^2.1.0", + "next": "^14.2.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "shiki": "^1.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.24", + "eslint": "^8.0.0", + "eslint-config-next": "^14.2.0", + "postcss": "^8.0.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.0.0" + } +} diff --git a/sdk-showcase/pnpm-lock.yaml b/sdk-showcase/pnpm-lock.yaml new file mode 100644 index 00000000..abf3186b --- /dev/null +++ b/sdk-showcase/pnpm-lock.yaml @@ -0,0 +1,4071 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@tanstack/react-query': + specifier: ^5.0.0 + version: 5.90.20(react@18.3.1) + '@terminal49/sdk': + specifier: link:../sdks/typescript-sdk + version: link:../sdks/typescript-sdk + clsx: + specifier: ^2.1.0 + version: 2.1.1 + next: + specifier: ^14.2.0 + version: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.3.0 + version: 18.3.1 + react-dom: + specifier: ^18.3.0 + version: 18.3.1(react@18.3.1) + shiki: + specifier: ^1.0.0 + version: 1.29.2 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.31 + '@types/react': + specifier: ^18.3.0 + version: 18.3.27 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.27) + autoprefixer: + specifier: ^10.4.24 + version: 10.4.24(postcss@8.5.6) + eslint: + specifier: ^8.0.0 + version: 8.57.1 + eslint-config-next: + specifier: ^14.2.0 + version: 14.2.35(eslint@8.57.1)(typescript@5.9.3) + postcss: + specifier: ^8.0.0 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.0 + version: 3.4.19 + typescript: + specifier: ^5.0.0 + version: 5.9.3 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@next/env@14.2.35': + resolution: {integrity: sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==} + + '@next/eslint-plugin-next@14.2.35': + resolution: {integrity: sha512-Jw9A3ICz2183qSsqwi7fgq4SBPiNfmOLmTPXKvlnzstUwyvBrtySiY+8RXJweNAs9KThb1+bYhZh9XWcNOr2zQ==} + + '@next/swc-darwin-arm64@14.2.33': + resolution: {integrity: sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@14.2.33': + resolution: {integrity: sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@14.2.33': + resolution: {integrity: sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@14.2.33': + resolution: {integrity: sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@14.2.33': + resolution: {integrity: sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@14.2.33': + resolution: {integrity: sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@14.2.33': + resolution: {integrity: sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-ia32-msvc@14.2.33': + resolution: {integrity: sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@next/swc-win32-x64-msvc@14.2.33': + resolution: {integrity: sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@rushstack/eslint-patch@1.15.0': + resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==} + + '@shikijs/core@1.29.2': + resolution: {integrity: sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==} + + '@shikijs/engine-javascript@1.29.2': + resolution: {integrity: sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==} + + '@shikijs/engine-oniguruma@1.29.2': + resolution: {integrity: sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==} + + '@shikijs/langs@1.29.2': + resolution: {integrity: sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==} + + '@shikijs/themes@1.29.2': + resolution: {integrity: sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==} + + '@shikijs/types@1.29.2': + resolution: {integrity: sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.5': + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + + '@tanstack/react-query@5.90.20': + resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==} + peerDependencies: + react: ^18 || ^19 + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/node@20.19.31': + resolution: {integrity: sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@typescript-eslint/eslint-plugin@8.54.0': + resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.54.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.54.0': + resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.54.0': + resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.54.0': + resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.54.0': + resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.54.0': + resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.54.0': + resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.54.0': + resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.54.0': + resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.54.0': + resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + autoprefixer@10.4.24: + resolution: {integrity: sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.1: + resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001767: + resolution: {integrity: sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.2: + resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@14.2.35: + resolution: {integrity: sha512-BpLsv01UisH193WyT/1lpHqq5iJ/Orfz9h/NOOlAmTUq4GY349PextQ62K4XpnaM9supeiEn3TaOTeQO07gURg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705: + resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.1: + resolution: {integrity: sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next@14.2.35: + resolution: {integrity: sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + oniguruma-to-es@2.3.0: + resolution: {integrity: sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regex-recursion@5.1.1: + resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@5.1.1: + resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@1.29.2: + resolution: {integrity: sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-jsx@5.1.1: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@next/env@14.2.35': {} + + '@next/eslint-plugin-next@14.2.35': + dependencies: + glob: 10.3.10 + + '@next/swc-darwin-arm64@14.2.33': + optional: true + + '@next/swc-darwin-x64@14.2.33': + optional: true + + '@next/swc-linux-arm64-gnu@14.2.33': + optional: true + + '@next/swc-linux-arm64-musl@14.2.33': + optional: true + + '@next/swc-linux-x64-gnu@14.2.33': + optional: true + + '@next/swc-linux-x64-musl@14.2.33': + optional: true + + '@next/swc-win32-arm64-msvc@14.2.33': + optional: true + + '@next/swc-win32-ia32-msvc@14.2.33': + optional: true + + '@next/swc-win32-x64-msvc@14.2.33': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@rushstack/eslint-patch@1.15.0': {} + + '@shikijs/core@1.29.2': + dependencies: + '@shikijs/engine-javascript': 1.29.2 + '@shikijs/engine-oniguruma': 1.29.2 + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 2.3.0 + + '@shikijs/engine-oniguruma@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + + '@shikijs/themes@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + + '@shikijs/types@1.29.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.5': + dependencies: + '@swc/counter': 0.1.3 + tslib: 2.8.1 + + '@tanstack/query-core@5.90.20': {} + + '@tanstack/react-query@5.90.20(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.90.20 + react: 18.3.1 + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/json5@0.0.29': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@20.19.31': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.27)': + dependencies: + '@types/react': 18.3.27 + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/unist@3.0.3': {} + + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/type-utils': 8.54.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + eslint: 8.57.1 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + eslint: 8.57.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.54.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.54.0': {} + + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.54.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + eslint: 8.57.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + eslint-visitor-keys: 4.2.1 + + '@ungap/structured-clone@1.3.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + ast-types-flow@0.0.8: {} + + async-function@1.0.0: {} + + autoprefixer@10.4.24(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001767 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.1: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.9.19: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001767 + electron-to-chromium: 1.5.286 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001767: {} + + ccount@2.0.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.286: {} + + emoji-regex-xs@1.0.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.2: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-next@14.2.35(eslint@8.57.1)(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 14.2.35 + '@rushstack/eslint-patch': 1.15.0 + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) + eslint-plugin-react: 7.37.5(eslint@8.57.1) + eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 8.57.1 + get-tsconfig: 4.13.1 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.1 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.57.1 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-react@7.37.5(eslint@8.57.1): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.2 + eslint: 8.57.1 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fraction.js@5.3.4: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.13.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.3.10: + dependencies: + foreground-child: 3.3.1 + jackspeak: 2.3.6 + minimatch: 9.0.5 + minipass: 7.1.2 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + html-void-elements@3.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.3 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + math-intrinsics@1.1.0: {} + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + merge2@1.4.1: {} + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 14.2.35 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001767 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.33 + '@next/swc-darwin-x64': 14.2.33 + '@next/swc-linux-arm64-gnu': 14.2.33 + '@next/swc-linux-arm64-musl': 14.2.33 + '@next/swc-linux-x64-gnu': 14.2.33 + '@next/swc-linux-x64-musl': 14.2.33 + '@next/swc-win32-arm64-msvc': 14.2.33 + '@next/swc-win32-ia32-msvc': 14.2.33 + '@next/swc-win32-x64-msvc': 14.2.33 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + oniguruma-to-es@2.3.0: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 5.1.1 + regex-recursion: 5.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + possible-typed-array-names@1.1.0: {} + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@16.13.1: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regex-recursion@5.1.1: + dependencies: + regex: 5.1.1 + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@5.1.1: + dependencies: + regex-utilities: 2.3.0 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.7.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@1.29.2: + dependencies: + '@shikijs/core': 1.29.2 + '@shikijs/engine-javascript': 1.29.2 + '@shikijs/engine-oniguruma': 1.29.2 + '@shikijs/langs': 1.29.2 + '@shikijs/themes': 1.29.2 + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + stable-hash@0.0.5: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + streamsearch@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.1(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-lines@3.0.1: {} + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.9.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/sdk-showcase/postcss.config.mjs b/sdk-showcase/postcss.config.mjs new file mode 100644 index 00000000..7455d3eb --- /dev/null +++ b/sdk-showcase/postcss.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; + +export default config; diff --git a/sdk-showcase/src/app/api/containers/[id]/refresh/route.ts b/sdk-showcase/src/app/api/containers/[id]/refresh/route.ts new file mode 100644 index 00000000..1d7c793f --- /dev/null +++ b/sdk-showcase/src/app/api/containers/[id]/refresh/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getClient, hasApiToken } from '@/lib/terminal49'; + +/** + * POST /api/containers/[id]/refresh + * + * SDK Method: client.containers.refresh(id) + * + * Forces a refresh of container data from the carrier's system. + */ +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + if (!hasApiToken()) { + return NextResponse.json( + { error: 'T49_API_TOKEN environment variable is not set' }, + { status: 500 } + ); + } + + const { id } = await params; + + try { + const client = getClient(); + await client.containers.refresh(id); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Refresh container error:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to refresh container' }, + { status: 500 } + ); + } +} diff --git a/sdk-showcase/src/app/api/shipments/[id]/resume/route.ts b/sdk-showcase/src/app/api/shipments/[id]/resume/route.ts new file mode 100644 index 00000000..11bbe353 --- /dev/null +++ b/sdk-showcase/src/app/api/shipments/[id]/resume/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getClient, hasApiToken } from '@/lib/terminal49'; + +/** + * POST /api/shipments/[id]/resume + * + * SDK Method: client.shipments.resumeTracking(id) + * + * Resumes tracking a previously stopped shipment. + */ +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + if (!hasApiToken()) { + return NextResponse.json( + { error: 'T49_API_TOKEN environment variable is not set' }, + { status: 500 } + ); + } + + const { id } = await params; + + try { + const client = getClient(); + await client.shipments.resumeTracking(id); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Resume tracking error:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to resume tracking' }, + { status: 500 } + ); + } +} diff --git a/sdk-showcase/src/app/api/shipments/[id]/stop/route.ts b/sdk-showcase/src/app/api/shipments/[id]/stop/route.ts new file mode 100644 index 00000000..dede565e --- /dev/null +++ b/sdk-showcase/src/app/api/shipments/[id]/stop/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getClient, hasApiToken } from '@/lib/terminal49'; + +/** + * POST /api/shipments/[id]/stop + * + * SDK Method: client.shipments.stopTracking(id) + * + * Stops tracking a shipment. The shipment will no longer receive updates. + */ +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + if (!hasApiToken()) { + return NextResponse.json( + { error: 'T49_API_TOKEN environment variable is not set' }, + { status: 500 } + ); + } + + const { id } = await params; + + try { + const client = getClient(); + await client.shipments.stopTracking(id); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Stop tracking error:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to stop tracking' }, + { status: 500 } + ); + } +} diff --git a/sdk-showcase/src/app/api/tracking-requests/create/route.ts b/sdk-showcase/src/app/api/tracking-requests/create/route.ts new file mode 100644 index 00000000..ed1a0d4e --- /dev/null +++ b/sdk-showcase/src/app/api/tracking-requests/create/route.ts @@ -0,0 +1,63 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getClient, hasApiToken } from '@/lib/terminal49'; + +/** + * POST /api/tracking-requests/create + * + * SDK Methods: + * - client.trackingRequests.createFromInfer(number, options) + * - client.trackingRequests.create(params) + * + * Creates a tracking request, optionally using inference results. + */ +export async function POST(request: NextRequest) { + if (!hasApiToken()) { + return NextResponse.json( + { error: 'T49_API_TOKEN environment variable is not set' }, + { status: 500 } + ); + } + + try { + const body = await request.json(); + const { number, scac } = body; + + if (!number || typeof number !== 'string') { + return NextResponse.json( + { error: 'Missing or invalid tracking number' }, + { status: 400 } + ); + } + + const client = getClient(); + + // Use createFromInfer if SCAC is provided (from inference flow) + // Otherwise use trackContainer for a simpler flow + let result; + + if (scac) { + // Create with explicit SCAC from inference + result = await client.trackingRequests.createFromInfer(number.trim(), { + scac, + }); + } else { + // Use simpler track method that handles inference internally + result = await client.trackContainer({ containerNumber: number.trim() }); + } + + return NextResponse.json({ + success: true, + trackingRequest: result.trackingRequest || result, + shipment: result.shipment, + }); + } catch (error) { + console.error('Create tracking request error:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Failed to create tracking request', + }, + { status: 500 } + ); + } +} diff --git a/sdk-showcase/src/app/api/tracking-requests/infer/route.ts b/sdk-showcase/src/app/api/tracking-requests/infer/route.ts new file mode 100644 index 00000000..4913d3e9 --- /dev/null +++ b/sdk-showcase/src/app/api/tracking-requests/infer/route.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getClient, hasApiToken } from '@/lib/terminal49'; + +/** + * POST /api/tracking-requests/infer + * + * SDK Method: client.trackingRequests.inferNumber(number) + * + * Infers the number type (container, booking, bill of lading) and + * suggests carrier candidates based on the tracking number format. + */ +export async function POST(request: NextRequest) { + if (!hasApiToken()) { + return NextResponse.json( + { error: 'T49_API_TOKEN environment variable is not set' }, + { status: 500 } + ); + } + + try { + const body = await request.json(); + const { number } = body; + + if (!number || typeof number !== 'string') { + return NextResponse.json( + { error: 'Missing or invalid tracking number' }, + { status: 400 } + ); + } + + const client = getClient(); + const result = await client.trackingRequests.inferNumber(number.trim()); + + return NextResponse.json(result); + } catch (error) { + console.error('Infer number error:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to infer number' }, + { status: 500 } + ); + } +} diff --git a/sdk-showcase/src/app/containers/[id]/demurrage/page.tsx b/sdk-showcase/src/app/containers/[id]/demurrage/page.tsx new file mode 100644 index 00000000..1c2e944b --- /dev/null +++ b/sdk-showcase/src/app/containers/[id]/demurrage/page.tsx @@ -0,0 +1,565 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Container Demurrage Page + * + * SDK Methods demonstrated: + * - client.getDemurrage(containerId) - Get LFD, fees, and holds information + * - client.getRailMilestones(containerId) - Get rail tracking (North America) + * - client.containers.get(id) - Get container details for context + */ + +interface PageProps { + params: Promise<{ id: string }>; +} + +interface Fee { + type?: string; + description?: string; + amount?: number | string; + currency?: string; + start_date?: string; + startDate?: string; + end_date?: string; + endDate?: string; +} + +interface Hold { + type?: string; + status?: string; + description?: string; +} + +interface DemurrageData { + pickup_lfd?: string; + pickupLfd?: string; + last_free_day?: string; + lastFreeDay?: string; + available_for_pickup?: boolean; + availableForPickup?: boolean; + available_for_pickup_at?: string; + availableForPickupAt?: string; + fees_at_pod_terminal?: Fee[]; + feesAtPodTerminal?: Fee[]; + fees?: Fee[]; + holds_at_pod_terminal?: Hold[]; + holdsAtPodTerminal?: Hold[]; + holds?: Hold[] | string[]; + demurrage_per_diem?: number; + demurragePerDiem?: number; + demurrage_currency?: string; + demurrageCurrency?: string; +} + +interface RailMilestones { + pod_rail_loaded_at?: string; + podRailLoadedAt?: string; + pod_rail_departed_at?: string; + podRailDepartedAt?: string; + ind_rail_arrived_at?: string; + indRailArrivedAt?: string; + ind_rail_unloaded_at?: string; + indRailUnloadedAt?: string; + rail_carrier_name?: string; + railCarrierName?: string; + rail_carrier_scac?: string; + railCarrierScac?: string; + rail_destination?: string; + railDestination?: string; + rail_events?: any[]; + railEvents?: any[]; +} + +async function getContainerDemurrage(id: string) { + if (!hasApiToken()) { + return { + container: null, + demurrage: null, + railMilestones: null, + error: 'T49_API_TOKEN environment variable is not set', + }; + } + + const client = getClient(); + + try { + // Fetch container details, demurrage, and rail milestones in parallel + const [container, demurrageResult, railResult] = await Promise.all([ + client.containers.get(id, ['shipment'], { format: 'mapped' }), + client.getDemurrage(id).catch(() => null), + client.getRailMilestones(id).catch(() => null), + ]); + + return { + container, + demurrage: demurrageResult, + railMilestones: railResult, + }; + } catch (error) { + console.error('Failed to fetch container demurrage:', error); + return { + container: null, + demurrage: null, + railMilestones: null, + error: error instanceof Error ? error.message : 'Failed to fetch container demurrage', + }; + } +} + +function calculateDaysUntilLfd(lfdDate: string): number { + const lfd = new Date(lfdDate); + const today = new Date(); + today.setHours(0, 0, 0, 0); + lfd.setHours(0, 0, 0, 0); + const diffTime = lfd.getTime() - today.getTime(); + return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); +} + +function getLfdStatus(daysRemaining: number): { color: string; label: string } { + if (daysRemaining < 0) { + return { color: 'bg-red-100 text-red-800 border-red-200', label: 'Overdue' }; + } + if (daysRemaining === 0) { + return { color: 'bg-red-100 text-red-800 border-red-200', label: 'Today!' }; + } + if (daysRemaining <= 2) { + return { color: 'bg-orange-100 text-orange-800 border-orange-200', label: 'Urgent' }; + } + if (daysRemaining <= 5) { + return { color: 'bg-yellow-100 text-yellow-800 border-yellow-200', label: 'Approaching' }; + } + return { color: 'bg-green-100 text-green-800 border-green-200', label: 'OK' }; +} + +export default async function ContainerDemurragePage({ params }: PageProps) { + const { id } = await params; + const { container, demurrage, railMilestones, error } = await getContainerDemurrage(id); + + if (!container && !error) { + notFound(); + } + + const number = container?.number; + const demurrageData = demurrage as DemurrageData | null; + const railData = railMilestones as RailMilestones | null; + + // Extract demurrage fields + const lfd = + demurrageData?.pickup_lfd || + demurrageData?.pickupLfd || + demurrageData?.last_free_day || + demurrageData?.lastFreeDay || + container?.pickup_lfd || + container?.pickupLfd; + const isAvailable = + demurrageData?.available_for_pickup ?? + demurrageData?.availableForPickup ?? + container?.available_for_pickup ?? + container?.availableForPickup; + const availableSince = + demurrageData?.available_for_pickup_at || + demurrageData?.availableForPickupAt || + container?.available_for_pickup_at || + container?.availableForPickupAt; + const fees = demurrageData?.fees_at_pod_terminal || demurrageData?.feesAtPodTerminal || demurrageData?.fees || []; + const holds = demurrageData?.holds_at_pod_terminal || demurrageData?.holdsAtPodTerminal || demurrageData?.holds || container?.holds || []; + const perDiem = demurrageData?.demurrage_per_diem || demurrageData?.demurragePerDiem; + const currency = demurrageData?.demurrage_currency || demurrageData?.demurrageCurrency || 'USD'; + + // Extract rail fields + const railLoaded = railData?.pod_rail_loaded_at || railData?.podRailLoadedAt; + const railDeparted = railData?.pod_rail_departed_at || railData?.podRailDepartedAt; + const railArrived = railData?.ind_rail_arrived_at || railData?.indRailArrivedAt; + const railUnloaded = railData?.ind_rail_unloaded_at || railData?.indRailUnloadedAt; + const railCarrier = railData?.rail_carrier_name || railData?.railCarrierName; + const railCarrierScac = railData?.rail_carrier_scac || railData?.railCarrierScac; + const railDestination = railData?.rail_destination || railData?.railDestination; + + const daysUntilLfd = lfd ? calculateDaysUntilLfd(lfd) : null; + const lfdStatus = daysUntilLfd !== null ? getLfdStatus(daysUntilLfd) : null; + + const demurrageCode = `// Get demurrage information +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get Last Free Day, fees, holds, and availability +const demurrage = await client.getDemurrage('${id}'); + +// Demurrage data includes: +// - pickup_lfd: Last Free Day before demurrage charges begin +// - available_for_pickup: Whether container is ready +// - available_for_pickup_at: When container became available +// - fees_at_pod_terminal: Array of fee objects with type, amount, dates +// - holds_at_pod_terminal: Array of holds (customs, freight, etc.) +// - demurrage_per_diem: Daily demurrage rate after LFD + +// Example response: +// { +// pickup_lfd: '2025-02-10', +// available_for_pickup: true, +// fees_at_pod_terminal: [ +// { type: 'demurrage', amount: 150, currency: 'USD' } +// ], +// holds_at_pod_terminal: [] +// }`; + + const railCode = `// Get rail milestones (North America) +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get rail tracking for inland delivery +const railMilestones = await client.getRailMilestones('${id}'); + +// Rail milestones include: +// - pod_rail_loaded_at: Container loaded onto rail at port +// - pod_rail_departed_at: Train departed from port +// - ind_rail_arrived_at: Train arrived at inland destination +// - ind_rail_unloaded_at: Container unloaded from rail +// - rail_carrier_name: Rail carrier (BNSF, UP, CSX, NS, etc.) +// - rail_destination: Inland rail destination city + +// Note: Rail milestones are only available for containers +// with inland rail delivery in North America`; + + return ( +
+ + +
+ } + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading demurrage

+

{error}

+
+ )} + + {/* Navigation Tabs */} +
+ + Overview + + + Events + + + Route + + + Demurrage + +
+ + {container && ( + <> + {/* LFD and Availability Cards */} +
+ {/* Last Free Day */} + + + Last Free Day (LFD) + + + {lfd ? ( +
+
+
+

{formatDate(lfd)}

+ {daysUntilLfd !== null && ( +

+ {daysUntilLfd < 0 + ? `${Math.abs(daysUntilLfd)} days overdue` + : daysUntilLfd === 0 + ? 'Due today!' + : `${daysUntilLfd} day${daysUntilLfd !== 1 ? 's' : ''} remaining`} +

+ )} +
+ {lfdStatus && ( + + {lfdStatus.label} + + )} +
+ + {/* Progress bar visualization */} + {daysUntilLfd !== null && daysUntilLfd >= 0 && ( +
+
+
+
+
+ )} + + {perDiem && ( +

+ Demurrage rate after LFD: {currency} {perDiem}/day +

+ )} +
+ ) : ( +

LFD not yet available

+ )} + + + + {/* Availability */} + + + Availability + + +
+
+ {isAvailable ? '✅' : '⏳'} +
+

+ {isAvailable ? 'Available for Pickup' : 'Not Yet Available'} +

+ {availableSince && ( +

+ Available since: {formatDate(availableSince)} +

+ )} +
+
+
+
+
+
+ + {/* Fees and Holds */} +
+ {/* Fees */} + + + Fees at Terminal + + + {fees.length > 0 ? ( +
+ {fees.map((fee: Fee, index: number) => ( +
+
+

{fee.type || fee.description || 'Fee'}

+ {(fee.start_date || fee.startDate) && ( +

+ From: {formatDate(fee.start_date || fee.startDate || '')} + {(fee.end_date || fee.endDate) && + ` to ${formatDate(fee.end_date || fee.endDate || '')}`} +

+ )} +
+ + {fee.currency || currency} {fee.amount || '—'} + +
+ ))} +
+ ) : ( +
+ +

No fees currently

+
+ )} +
+
+ + {/* Holds */} + + + Holds at Terminal + + + {holds.length > 0 ? ( +
+ {holds.map((hold: Hold | string, index: number) => { + const holdData = typeof hold === 'string' ? { type: hold } : hold; + return ( +
+
+ 🚫 +
+

+ {holdData.type || holdData.description || 'Hold'} +

+ {holdData.status && ( +

{holdData.status}

+ )} +
+
+
+ ); + })} +
+ ) : ( +
+ +

No holds - clear for pickup

+
+ )} +
+
+
+ + {/* SDK Code for Demurrage */} + + + {/* Rail Milestones */} + + + Rail Milestones (North America) + + + {railData || railCarrier || railLoaded || railArrived ? ( +
+ {/* Rail carrier info */} + {(railCarrier || railDestination) && ( +
+
+ 🚂 +
+

Rail Carrier

+

+ {railCarrier || '—'} + {railCarrierScac && ` (${railCarrierScac})`} +

+
+
+ {railDestination && ( +
+

Destination

+

{railDestination}

+
+ )} +
+ )} + + {/* Rail milestones timeline */} +
+
+

POD Rail Loaded

+

+ {railLoaded ? formatDate(railLoaded) : '—'} +

+
+
+

POD Rail Departed

+

+ {railDeparted ? formatDate(railDeparted) : '—'} +

+
+
+

IND Rail Arrived

+

+ {railArrived ? formatDate(railArrived) : '—'} +

+
+
+

IND Rail Unloaded

+

+ {railUnloaded ? formatDate(railUnloaded) : '—'} +

+
+
+
+ ) : ( +

+ Rail milestones not available for this container. +
+ + (Only available for containers with inland rail delivery in North America) + +

+ )} +
+
+ + {/* SDK Code for Rail */} + + + {/* Raw Data */} + {(demurrageData || railData) && ( + + + Raw API Response + + +
+                    
+                      {JSON.stringify({ demurrage: demurrageData, railMilestones: railData }, null, 2)}
+                    
+                  
+
+
+ )} + + )} +
+
+ ); +} diff --git a/sdk-showcase/src/app/containers/[id]/events/page.tsx b/sdk-showcase/src/app/containers/[id]/events/page.tsx new file mode 100644 index 00000000..db95a615 --- /dev/null +++ b/sdk-showcase/src/app/containers/[id]/events/page.tsx @@ -0,0 +1,335 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Container Events Page + * + * SDK Methods demonstrated: + * - client.containers.events(id) - Get transport events timeline + * - client.containers.rawEvents(id) - Get raw carrier events + */ + +interface PageProps { + params: Promise<{ id: string }>; +} + +interface TransportEvent { + id: string; + event_type?: string; + eventType?: string; + event?: string; + description?: string; + location?: string; + locationName?: string; + location_name?: string; + vessel?: string; + vesselName?: string; + vessel_name?: string; + voyage?: string; + voyageNumber?: string; + voyage_number?: string; + timestamp?: string; + event_time?: string; + eventTime?: string; + actual_time?: string; + actualTime?: string; + created_at?: string; + createdAt?: string; + source?: string; +} + +interface RawEvent { + id: string; + event?: string; + description?: string; + location?: string; + timestamp?: string; + raw_data?: any; + rawData?: any; + source?: string; +} + +async function getContainerEvents(id: string) { + if (!hasApiToken()) { + return { + container: null, + events: [], + rawEvents: [], + error: 'T49_API_TOKEN environment variable is not set', + }; + } + + const client = getClient(); + + try { + // Fetch container details, transport events, and raw events in parallel + const [container, eventsResult, rawEventsResult] = await Promise.all([ + client.containers.get(id, ['shipment'], { format: 'mapped' }), + client.containers.events(id, { format: 'mapped' }).catch(() => []), + client.containers.rawEvents(id, { format: 'mapped' }).catch(() => []), + ]); + + const events = Array.isArray(eventsResult) ? eventsResult : eventsResult?.items || []; + const rawEvents = Array.isArray(rawEventsResult) ? rawEventsResult : rawEventsResult?.items || []; + + return { container, events, rawEvents }; + } catch (error) { + console.error('Failed to fetch container events:', error); + return { + container: null, + events: [], + rawEvents: [], + error: error instanceof Error ? error.message : 'Failed to fetch container events', + }; + } +} + +function formatEventType(type: string): string { + return type + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +function getEventIcon(type: string): string { + const eventType = type?.toLowerCase() || ''; + if (eventType.includes('load')) return '📦'; + if (eventType.includes('depart')) return '🚢'; + if (eventType.includes('arrive')) return '⚓'; + if (eventType.includes('discharge')) return '🏗️'; + if (eventType.includes('gate') && eventType.includes('out')) return '🚚'; + if (eventType.includes('gate') && eventType.includes('in')) return '🚛'; + if (eventType.includes('rail')) return '🚂'; + if (eventType.includes('empty')) return '📭'; + if (eventType.includes('available')) return '✅'; + return '📍'; +} + +export default async function ContainerEventsPage({ params }: PageProps) { + const { id } = await params; + const { container, events, rawEvents, error } = await getContainerEvents(id); + + if (!container && !error) { + notFound(); + } + + const number = container?.number; + + const eventsCode = `// Get container transport events +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get standardized transport events timeline +const events = await client.containers.events('${id}', { + format: 'mapped' +}); + +// Transport events include: +// - event_type: vessel_loaded, vessel_departed, vessel_arrived, +// vessel_discharged, gate_out, empty_returned, etc. +// - timestamp: When the event occurred +// - location: Port or terminal where the event occurred +// - vessel/voyage: Ship information if applicable + +// Events are normalized across all carriers into a consistent format`; + + const rawEventsCode = `// Get raw carrier events (unprocessed) +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get raw events directly from the carrier +const rawEvents = await client.containers.rawEvents('${id}', { + format: 'mapped' +}); + +// Raw events contain the original carrier data before normalization. +// Useful for: +// - Debugging discrepancies +// - Accessing carrier-specific fields +// - Historical audit trails`; + + return ( +
+ + +
+ } + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading events

+

{error}

+
+ )} + + {/* Navigation Tabs */} +
+ + Overview + + + Events + + + Route + + + Demurrage + +
+ + {container && ( + <> + {/* Transport Events Timeline */} + + + Transport Events ({events.length}) + + + {events.length > 0 ? ( +
+ {/* Timeline line */} +
+ +
+ {events.map((event: TransportEvent, index: number) => { + const eventType = event.event_type || event.eventType || event.event || 'unknown'; + const eventTime = event.timestamp || event.event_time || event.eventTime || event.actual_time || event.actualTime || event.created_at || event.createdAt; + const location = event.location || event.locationName || event.location_name; + const vessel = event.vessel || event.vesselName || event.vessel_name; + const voyage = event.voyage || event.voyageNumber || event.voyage_number; + + return ( +
+ {/* Timeline icon */} +
+ {getEventIcon(eventType)} +
+ +
+
+
+

{formatEventType(eventType)}

+ {event.description && ( +

{event.description}

+ )} +
+ {location && ( + 📍 {location} + )} + {(vessel || voyage) && ( + + 🚢 {vessel} {voyage && `/ ${voyage}`} + + )} + {event.source && ( + + {event.source} + + )} +
+
+ + {eventTime ? formatDate(eventTime) : '—'} + +
+
+
+ ); + })} +
+
+ ) : ( +

No transport events recorded yet

+ )} + + + + {/* SDK Code for Transport Events */} + + + {/* Raw Events */} + + + Raw Carrier Events ({rawEvents.length}) + + + {rawEvents.length > 0 ? ( +
+ {rawEvents.map((event: RawEvent, index: number) => ( +
+
+
+

+ {event.event || 'Raw Event'} +

+ {event.description && ( +

{event.description}

+ )} + {event.location && ( +

📍 {event.location}

+ )} +
+ + {event.timestamp ? formatDate(event.timestamp) : '—'} + +
+ {(event.raw_data || event.rawData) && ( +
+ + View raw data + +
+                              {JSON.stringify(event.raw_data || event.rawData, null, 2)}
+                            
+
+ )} +
+ ))} +
+ ) : ( +

No raw events available

+ )} +
+
+ + {/* SDK Code for Raw Events */} + + + )} +
+
+ ); +} diff --git a/sdk-showcase/src/app/containers/[id]/page.tsx b/sdk-showcase/src/app/containers/[id]/page.tsx new file mode 100644 index 00000000..ee6445d2 --- /dev/null +++ b/sdk-showcase/src/app/containers/[id]/page.tsx @@ -0,0 +1,390 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button, StatusBadge } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { RefreshButton } from './refresh-button'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Container Detail Page + * + * SDK Methods demonstrated: + * - client.containers.get(id) - Get container details + * - client.containers.events(id) - Get container events timeline + * - client.containers.route(id) - Get container route info + * - client.containers.rawEvents(id) - Get raw carrier events + * - client.containers.refresh(id) - Force refresh container data + */ + +interface PageProps { + params: Promise<{ id: string }>; +} + +interface ContainerEvent { + id: string; + // SDK mapped format uses camelCase - 'event' field contains the event type + event?: string; + eventTime?: string; + description?: string; + // SDK maps location as nested object + location?: { + id: string; + name?: string; + locode?: string; + }; + // SDK maps terminal as nested object + terminal?: { + id: string; + name?: string; + nickname?: string; + firmsCode?: string; + }; + vessel?: string; + voyage?: string; + createdAt?: string; +} + +async function getContainerData(id: string) { + if (!hasApiToken()) { + return { + container: null, + events: [], + error: 'T49_API_TOKEN environment variable is not set', + }; + } + + const client = getClient(); + + try { + // Fetch container details and events in parallel + const [container, eventsResult] = await Promise.all([ + client.containers.get(id, ['shipment'], { format: 'mapped' }), + client.containers.events(id, { format: 'mapped' }).catch(() => []), + ]); + + const events = Array.isArray(eventsResult) ? eventsResult : eventsResult?.items || []; + + return { container, events }; + } catch (error) { + console.error('Failed to fetch container:', error); + return { + container: null, + events: [], + error: error instanceof Error ? error.message : 'Failed to fetch container', + }; + } +} + +function formatEventType(type: string): string { + return type + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export default async function ContainerDetailPage({ params }: PageProps) { + const { id } = await params; + const { container, events, error } = await getContainerData(id); + + if (!container && !error) { + notFound(); + } + + const detailCode = `// Get container details and events +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get container details +const container = await client.containers.get('${id}', { + format: 'mapped' +}); + +// Get container events timeline +const events = await client.containers.events('${id}', { + format: 'mapped' +}); + +// Get container route information +const route = await client.containers.route('${id}'); + +// Get raw carrier events (unprocessed) +const rawEvents = await client.containers.rawEvents('${id}'); + +// Force refresh container data from carrier +await client.containers.refresh('${id}');`; + + // Extract container fields using SDK mapped format (nested structures) + const number = container?.number; + const status = container?.status || 'unknown'; + // Equipment info from SDK nested structure + const equipmentType = container?.equipment?.type; + const equipmentLength = container?.equipment?.length; + const equipmentHeight = container?.equipment?.height; + const weight = container?.equipment?.weightLbs; + // SDK returns shippingLineScac (string only, no name object) + const carrierScac = container?.shippingLineScac; + const carrierName = carrierScac; // SDK doesn't include carrier name in mapped format + // Shipment relation + const shipmentId = container?.shipment?.id; + // Terminal info from SDK nested structure + const terminal = container?.terminals?.podTerminal?.name; + const terminalCode = container?.terminals?.podTerminal?.firmsCode; + // Demurrage info from SDK nested structure + const lfd = container?.demurrage?.pickupLfd; + const availableDate = container?.demurrage?.pickupAppointmentAt; + const holdsInfo = container?.demurrage?.holds || []; + const fees = container?.demurrage?.fees || []; + // Other fields + const seal = container?.sealNumber; + + // Timestamps from SDK mapped format + const polLoaded = container?.polLoadedAt; + const podDischarged = container?.podDischargedAt; + const podFullOut = container?.podFullOutAt; + const emptyReturned = container?.emptyReturnedAt; + + return ( +
+ + + {shipmentId && ( + + )} + +
+ } + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading container

+

{error}

+
+ )} + + {container && ( + <> + {/* Status Card */} + + +
+
+

Current Status

+ +
+
+

Container Number

+

{number}

+
+
+
+
+ + {/* Main Info */} +
+ + + Container Details + + +
+
+

Equipment Type

+

{equipmentType || '—'}

+
+
+

Carrier

+

{carrierName || '—'}

+ {carrierScac &&

SCAC: {carrierScac}

} +
+
+

Weight

+

{weight ? `${weight} kg` : '—'}

+
+
+

Seal Number

+

{seal || '—'}

+
+
+
+
+ + + + Terminal Information + + +
+
+

Terminal

+

{terminal || '—'}

+ {terminalCode &&

FIRMS: {terminalCode}

} +
+
+

Last Free Day (LFD)

+

{lfd ? formatDate(lfd) : '—'}

+
+
+

Available Date

+

{availableDate ? formatDate(availableDate) : '—'}

+
+
+

Holds

+ {holdsInfo.length > 0 ? ( +
+ {holdsInfo.map((hold: string, i: number) => ( + + {hold} + + ))} +
+ ) : ( + None + )} +
+
+
+
+
+ + {/* Key Timestamps */} + + + Key Milestones + + +
+
+

Loaded at Origin

+

{polLoaded ? formatDate(polLoaded) : '—'}

+
+
+

Discharged at POD

+

{podDischarged ? formatDate(podDischarged) : '—'}

+
+
+

Full Out

+

{podFullOut ? formatDate(podFullOut) : '—'}

+
+
+

Empty Returned

+

{emptyReturned ? formatDate(emptyReturned) : '—'}

+
+
+
+
+ + {/* Events Timeline */} + + + Events Timeline ({events.length}) + + + {events.length > 0 ? ( +
+ {/* Timeline line */} +
+ +
+ {events.map((event: ContainerEvent, index: number) => { + // SDK mapped format uses 'event' field for event type + const eventType = event.event || 'unknown'; + const eventTime = event.eventTime || event.createdAt; + + return ( +
+ {/* Timeline dot */} +
+ +
+
+
+

{formatEventType(eventType)}

+ {event.description && ( +

{event.description}

+ )} + {event.location && ( +

+ 📍 {event.location.name || event.location.locode || 'Unknown location'} +

+ )} + {event.terminal && ( +

🏭 {event.terminal.name}

+ )} + {(event.vessel || event.voyage) && ( +

+ 🚢 {event.vessel} {event.voyage && `/ ${event.voyage}`} +

+ )} +
+ + {eventTime ? formatDate(eventTime) : '—'} + +
+
+
+ ); + })} +
+
+ ) : ( +

No events recorded yet

+ )} + + + + {/* Fees (if any) */} + {fees.length > 0 && ( + + + Fees & Charges + + +
+ {fees.map((fee: any, index: number) => ( +
+ {fee.type || fee.description || 'Fee'} + ${fee.amount || '—'} +
+ ))} +
+
+
+ )} + + {/* Raw Data */} + + + Raw API Response + + +
+                  {JSON.stringify({ container, events }, null, 2)}
+                
+
+
+ + )} + + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/containers/[id]/refresh-button.tsx b/sdk-showcase/src/app/containers/[id]/refresh-button.tsx new file mode 100644 index 00000000..a85d75f9 --- /dev/null +++ b/sdk-showcase/src/app/containers/[id]/refresh-button.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Button } from '@/components/features'; + +interface RefreshButtonProps { + containerId: string; +} + +export function RefreshButton({ containerId }: RefreshButtonProps) { + const [loading, setLoading] = useState(false); + const router = useRouter(); + + async function handleRefresh() { + setLoading(true); + + try { + const response = await fetch(`/api/containers/${containerId}/refresh`, { + method: 'POST', + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Refresh failed'); + } + + // Refresh the page to show updated data + router.refresh(); + } catch (error) { + console.error('Refresh error:', error); + alert(error instanceof Error ? error.message : 'Refresh failed'); + } finally { + setLoading(false); + } + } + + return ( + + ); +} diff --git a/sdk-showcase/src/app/containers/[id]/route/page.tsx b/sdk-showcase/src/app/containers/[id]/route/page.tsx new file mode 100644 index 00000000..8410a489 --- /dev/null +++ b/sdk-showcase/src/app/containers/[id]/route/page.tsx @@ -0,0 +1,397 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Container Route Page + * + * SDK Methods demonstrated: + * - client.containers.route(id) - Get container route with legs and locations + * - client.containers.get(id) - Get container details for context + */ + +interface PageProps { + params: Promise<{ id: string }>; +} + +interface RouteLeg { + id?: string; + leg_number?: number; + legNumber?: number; + mode?: string; + transport_mode?: string; + transportMode?: string; + vessel_name?: string; + vesselName?: string; + vessel_imo?: string; + vesselImo?: string; + voyage_number?: string; + voyageNumber?: string; + origin_name?: string; + originName?: string; + origin_locode?: string; + originLocode?: string; + destination_name?: string; + destinationName?: string; + destination_locode?: string; + destinationLocode?: string; + etd?: string; + atd?: string; + eta?: string; + ata?: string; + etd_at?: string; + etdAt?: string; + atd_at?: string; + atdAt?: string; + eta_at?: string; + etaAt?: string; + ata_at?: string; + ataAt?: string; +} + +interface RouteData { + legs?: RouteLeg[]; + origin?: { + name?: string; + locode?: string; + }; + destination?: { + name?: string; + locode?: string; + }; +} + +async function getContainerRoute(id: string) { + if (!hasApiToken()) { + return { + container: null, + route: null, + error: 'T49_API_TOKEN environment variable is not set', + }; + } + + const client = getClient(); + + try { + // Fetch container details and route in parallel + const [container, routeResult] = await Promise.all([ + client.containers.get(id, ['shipment'], { format: 'mapped' }), + client.containers.route(id, { format: 'mapped' }).catch(() => null), + ]); + + return { container, route: routeResult }; + } catch (error) { + console.error('Failed to fetch container route:', error); + return { + container: null, + route: null, + error: error instanceof Error ? error.message : 'Failed to fetch container route', + }; + } +} + +function getModeIcon(mode: string): string { + const modeType = mode?.toLowerCase() || ''; + if (modeType.includes('vessel') || modeType.includes('ocean') || modeType.includes('sea')) return '🚢'; + if (modeType.includes('rail') || modeType.includes('train')) return '🚂'; + if (modeType.includes('truck') || modeType.includes('road')) return '🚚'; + if (modeType.includes('barge')) return '⛵'; + if (modeType.includes('air')) return '✈️'; + return '📍'; +} + +function formatMode(mode: string): string { + if (!mode) return 'Unknown'; + return mode + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export default async function ContainerRoutePage({ params }: PageProps) { + const { id } = await params; + const { container, route, error } = await getContainerRoute(id); + + if (!container && !error) { + notFound(); + } + + const number = container?.number; + const routeData = route as RouteData | null; + const legs = routeData?.legs || []; + + const routeCode = `// Get container route information +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get route with all legs and locations +const route = await client.containers.route('${id}', { + format: 'mapped' +}); + +// Route includes: +// - legs: Array of route segments (ocean, rail, truck, etc.) +// - Each leg has origin/destination, vessel/voyage, ETD/ETA/ATD/ATA +// - Legs are ordered by leg_number (1 = first leg) + +// Example leg structure: +// { +// leg_number: 1, +// mode: 'vessel', +// vessel_name: 'EVER GIVEN', +// voyage_number: '1234E', +// origin_name: 'Shanghai', +// destination_name: 'Los Angeles', +// etd: '2025-01-15T10:00:00Z', +// eta: '2025-02-01T08:00:00Z' +// }`; + + return ( +
+ + +
+ } + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading route

+

{error}

+
+ )} + + {/* Navigation Tabs */} +
+ + Overview + + + Events + + + Route + + + Demurrage + +
+ + {container && ( + <> + {/* Route Summary */} + + + Route Overview + + + {legs.length > 0 ? ( +
+ {/* Origin */} +
+ 🏭 +
+

+ {legs[0]?.origin_name || legs[0]?.originName || 'Origin'} +

+

+ {legs[0]?.origin_locode || legs[0]?.originLocode || '—'} +

+
+
+ + {/* Arrow with leg count */} +
+
+
+ + {legs.length} leg{legs.length !== 1 ? 's' : ''} + +
+ +
+
+ + {/* Destination */} +
+ 🏭 +
+

+ {legs[legs.length - 1]?.destination_name || + legs[legs.length - 1]?.destinationName || + 'Destination'} +

+

+ {legs[legs.length - 1]?.destination_locode || + legs[legs.length - 1]?.destinationLocode || + '—'} +

+
+
+
+ ) : ( +

No route information available

+ )} + + + + {/* Route Legs */} + + + Route Legs ({legs.length}) + + + {legs.length > 0 ? ( +
+ {legs.map((leg: RouteLeg, index: number) => { + const legNumber = leg.leg_number || leg.legNumber || index + 1; + const mode = leg.mode || leg.transport_mode || leg.transportMode || 'unknown'; + const vesselName = leg.vessel_name || leg.vesselName; + const vesselImo = leg.vessel_imo || leg.vesselImo; + const voyageNumber = leg.voyage_number || leg.voyageNumber; + const originName = leg.origin_name || leg.originName; + const originLocode = leg.origin_locode || leg.originLocode; + const destName = leg.destination_name || leg.destinationName; + const destLocode = leg.destination_locode || leg.destinationLocode; + const etd = leg.etd || leg.etd_at || leg.etdAt; + const atd = leg.atd || leg.atd_at || leg.atdAt; + const eta = leg.eta || leg.eta_at || leg.etaAt; + const ata = leg.ata || leg.ata_at || leg.ataAt; + + return ( +
+ {/* Leg Header */} +
+
+ {getModeIcon(mode)} +
+

+ Leg {legNumber}: {formatMode(mode)} +

+ {(vesselName || voyageNumber) && ( +

+ {vesselName} + {voyageNumber && ` / Voyage ${voyageNumber}`} + {vesselImo && ` (IMO: ${vesselImo})`} +

+ )} +
+
+ + {ata ? 'Arrived' : atd ? 'In Transit' : 'Pending'} + +
+ + {/* Origin and Destination */} +
+ {/* Origin */} +
+

Origin

+

{originName || '—'}

+ {originLocode && ( +

{originLocode}

+ )} +
+
+ ETD: + {etd ? formatDate(etd) : '—'} +
+
+ ATD: + + {atd ? formatDate(atd) : '—'} + +
+
+
+ + {/* Destination */} +
+

Destination

+

{destName || '—'}

+ {destLocode && ( +

{destLocode}

+ )} +
+
+ ETA: + {eta ? formatDate(eta) : '—'} +
+
+ ATA: + + {ata ? formatDate(ata) : '—'} + +
+
+
+
+
+ ); + })} +
+ ) : ( +

No route legs available

+ )} +
+
+ + {/* Raw Route Data */} + {routeData && ( + + + Raw Route Response + + +
+                    {JSON.stringify(routeData, null, 2)}
+                  
+
+
+ )} + + {/* SDK Code Panel */} + + + )} +
+
+ ); +} diff --git a/sdk-showcase/src/app/containers/page.tsx b/sdk-showcase/src/app/containers/page.tsx new file mode 100644 index 00000000..66f36952 --- /dev/null +++ b/sdk-showcase/src/app/containers/page.tsx @@ -0,0 +1,250 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button, StatusBadge } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate, containerStatusConfig } from '@/lib/utils'; +import Link from 'next/link'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Containers List Page + * + * SDK Methods demonstrated: + * - client.containers.list(filters, options) - List and filter containers + */ + +interface Container { + id: string; + number?: string; + // SDK mapped format uses flat 'status' property + status?: string; + // SDK uses nested equipment structure + equipment?: { + type?: string; + length?: number; + height?: number; + weightLbs?: number; + }; + // SDK uses nested terminals structure + terminals?: { + podTerminal?: { + id: string; + name?: string; + nickname?: string; + firmsCode?: string; + } | null; + destinationTerminal?: { + id: string; + name?: string; + nickname?: string; + firmsCode?: string; + } | null; + }; + // SDK uses nested demurrage structure + demurrage?: { + pickupLfd?: string; + pickupAppointmentAt?: string; + fees?: any[]; + holds?: any[]; + }; + // SDK returns shippingLineScac (string, not object) + shippingLineScac?: string; + // Shipment relation + shipment?: { id?: string; billOfLading?: string }; + createdAt?: string; +} + +async function getContainers() { + if (!hasApiToken()) { + return { + containers: [], + error: 'T49_API_TOKEN environment variable is not set. Get your API key from https://app.terminal49.com/settings/api', + }; + } + + const client = getClient(); + + try { + const result = await client.containers.list({}, { pageSize: 50, format: 'mapped' }); + // SDK returns { items: [], links: ..., meta: ... } for mapped format + const containers = Array.isArray(result) ? result : result?.items || []; + + return { containers }; + } catch (error) { + console.error('Failed to fetch containers:', error); + return { + containers: [], + error: error instanceof Error ? error.message : 'Failed to fetch containers', + }; + } +} + +export default async function ContainersPage() { + const { containers, error } = await getContainers(); + + const listCode = `// List containers with filters and pagination +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get all containers +const containers = await client.containers.list( + {}, // filters (optional) + { pageSize: 50, format: 'mapped' } +); + +// Filter by status +const availableContainers = await client.containers.list( + { status: 'available' }, + { format: 'mapped' } +); + +// Filter by terminal +const lbctContainers = await client.containers.list( + { terminal: 'LBCT' }, + { format: 'mapped' } +); + +// Filter by carrier +const maerskContainers = await client.containers.list( + { carrier: 'MAEU' }, + { format: 'mapped' } +);`; + + // Calculate status counts + const statusCounts = containers.reduce((acc: Record, c: Container) => { + const status = c.status || 'unknown'; + acc[status] = (acc[status] || 0) + 1; + return acc; + }, {}); + + const topStatuses = Object.entries(statusCounts) + .sort((a, b) => (b[1] as number) - (a[1] as number)) + .slice(0, 4) as [string, number][]; + + return ( +
+ + Track New} + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading containers

+

{error}

+
+ )} + + {/* Stats Summary */} +
+ + +

{containers.length}

+

Total Containers

+
+
+ {topStatuses.map(([status, count]) => { + const config = containerStatusConfig[status] || { label: status, color: 'bg-gray-500' }; + return ( + + +

+ {count} +

+

{config.label}

+
+
+ ); + })} +
+ + {/* Containers Table */} + + + All Containers + + + {containers.length > 0 ? ( +
+ + + + + + + + + + + + + + {containers.map((container: Container) => { + // SDK mapped format uses nested structures + const status = container.status || 'unknown'; + const equipment = container.equipment?.type || '—'; + const terminal = container.terminals?.podTerminal?.name || '—'; + const carrier = container.shippingLineScac || '—'; + const lfd = container.demurrage?.pickupLfd; + + return ( + + + + + + + + + + ); + })} + +
NumberEquipmentStatusTerminalCarrierLFD
+ + {container.number} + + {equipment} + + {terminal}{carrier} + {lfd ? ( + {formatDate(lfd)} + ) : ( + + )} + + + Details → + +
+
+ ) : ( +
+

No containers found.

+ + Start tracking a container + +
+ )} +
+
+ + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/globals.css b/sdk-showcase/src/app/globals.css new file mode 100644 index 00000000..daa30bba --- /dev/null +++ b/sdk-showcase/src/app/globals.css @@ -0,0 +1,90 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + /* Clean, minimal light theme */ + --kumo-bg-base: #ffffff; + --kumo-bg-elevated: #ffffff; + --kumo-bg-recessed: #f8fafc; + + --kumo-text-default: #0f172a; + --kumo-text-secondary: #475569; + --kumo-text-muted: #94a3b8; + --kumo-text-link: #2563eb; + + --kumo-border-line: #e2e8f0; + --kumo-border-focus: #3b82f6; + + --kumo-accent: #2563eb; + --kumo-accent-hover: #1d4ed8; +} + +/* Dark theme - activated via data-theme="dark" on html element */ +[data-theme="dark"] { + --kumo-bg-base: #0f172a; + --kumo-bg-elevated: #1e293b; + --kumo-bg-recessed: #0f172a; + + --kumo-text-default: #f1f5f9; + --kumo-text-secondary: #cbd5e1; + --kumo-text-muted: #64748b; + --kumo-text-link: #60a5fa; + + --kumo-border-line: #334155; + --kumo-border-focus: #3b82f6; + + --kumo-accent: #3b82f6; + --kumo-accent-hover: #60a5fa; +} + +body { + background: var(--kumo-bg-base); + color: var(--kumo-text-default); + font-feature-settings: "cv02", "cv03", "cv04", "cv11"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Tighter typography */ +h1, h2, h3, h4, h5, h6 { + letter-spacing: -0.025em; +} + +/* Custom scrollbar - minimal */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--kumo-border-line); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--kumo-text-muted); +} + +/* Code panel scrollbar */ +.code-panel::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.code-panel::-webkit-scrollbar-track { + background: transparent; +} + +.code-panel::-webkit-scrollbar-thumb { + background: rgba(148, 163, 184, 0.3); + border-radius: 3px; +} + +.code-panel::-webkit-scrollbar-thumb:hover { + background: rgba(148, 163, 184, 0.5); +} diff --git a/sdk-showcase/src/app/layout.tsx b/sdk-showcase/src/app/layout.tsx new file mode 100644 index 00000000..2ec89d90 --- /dev/null +++ b/sdk-showcase/src/app/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import './globals.css'; +import { Sidebar } from '@/components/layout'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'Terminal49 SDK Showcase', + description: + 'A comprehensive showcase of the Terminal49 TypeScript SDK for container tracking', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +
+ +
{children}
+
+ + + ); +} diff --git a/sdk-showcase/src/app/page.tsx b/sdk-showcase/src/app/page.tsx new file mode 100644 index 00000000..618dfe4f --- /dev/null +++ b/sdk-showcase/src/app/page.tsx @@ -0,0 +1,318 @@ +import { PageHeader } from '@/components/layout'; +import { MetricCard, Card, CardHeader, CardTitle, CardContent, Button, StatusBadge } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate, daysUntil, containerStatusConfig } from '@/lib/utils'; +import Link from 'next/link'; + +// Force dynamic rendering - we fetch live data from the API +export const dynamic = 'force-dynamic'; + +/** + * Dashboard page + * + * SDK Methods demonstrated: + * - client.shipments.list() - Get shipment counts and summaries + * - client.containers.list() - Get container counts by status + * - client.trackingRequests.list() - Get pending tracking requests + */ + +async function getDashboardData() { + // Check if API token is configured before trying to use the client + if (!hasApiToken()) { + return { + totalShipments: 0, + totalContainers: 0, + inTransit: 0, + available: 0, + urgentLfd: 0, + pendingRequests: 0, + statusCounts: {}, + urgentContainers: [], + error: 'T49_API_TOKEN environment variable is not set. Get your API key from https://app.terminal49.com/settings/api', + }; + } + + const client = getClient(); + + try { + // Fetch data in parallel for performance + const [shipmentsResult, containersResult, trackingRequestsResult] = + await Promise.all([ + client.shipments.list({}, { pageSize: 100, format: 'mapped' }), + client.containers.list({}, { pageSize: 100, format: 'mapped' }), + client.trackingRequests.list({}, { pageSize: 100, format: 'mapped' }), + ]); + + // Extract data arrays - SDK returns { items: [], links: ..., meta: ... } for mapped format + const shipments = Array.isArray(shipmentsResult) + ? shipmentsResult + : shipmentsResult?.items || []; + const containers = Array.isArray(containersResult) + ? containersResult + : containersResult?.items || []; + const trackingRequests = Array.isArray(trackingRequestsResult) + ? trackingRequestsResult + : trackingRequestsResult?.items || []; + + // Calculate status distribution + const statusCounts: Record = {}; + containers.forEach((container: any) => { + const status = container.current_status || container.currentStatus || 'unknown'; + statusCounts[status] = (statusCounts[status] || 0) + 1; + }); + + // Find containers with LFD within 3 days + const urgentContainers = containers + .filter((container: any) => { + const lfd = container.pickup_lfd || container.pickupLfd; + if (!lfd) return false; + const days = daysUntil(lfd); + return days !== null && days >= 0 && days <= 3; + }) + .map((container: any) => ({ + id: container.id, + number: container.number, + lfd: container.pickup_lfd || container.pickupLfd, + daysRemaining: daysUntil(container.pickup_lfd || container.pickupLfd), + terminal: container.pod_terminal_name || container.podTerminalName || 'Unknown', + })) + .sort((a: any, b: any) => (a.daysRemaining || 0) - (b.daysRemaining || 0)) + .slice(0, 5); + + // Count in-transit shipments + const inTransitCount = shipments.filter( + (s: any) => s.status === 'in_transit' || s.status === 'active' + ).length; + + // Count available containers + const availableCount = statusCounts['available'] || 0; + + return { + totalShipments: shipments.length, + totalContainers: containers.length, + inTransit: inTransitCount, + available: availableCount, + urgentLfd: urgentContainers.length, + pendingRequests: trackingRequests.filter( + (r: any) => r.status === 'pending' || r.request_status === 'pending' + ).length, + statusCounts, + urgentContainers, + }; + } catch (error) { + console.error('Dashboard data fetch error:', error); + return { + totalShipments: 0, + totalContainers: 0, + inTransit: 0, + available: 0, + urgentLfd: 0, + pendingRequests: 0, + statusCounts: {}, + urgentContainers: [], + error: error instanceof Error ? error.message : 'Failed to fetch data', + }; + } +} + +export default async function DashboardPage() { + const data = await getDashboardData(); + + const dashboardCode = `// Fetch dashboard data using the Terminal49 SDK +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Fetch data in parallel for performance +const [shipments, containers, trackingRequests] = await Promise.all([ + client.shipments.list({}, { pageSize: 100, format: 'mapped' }), + client.containers.list({}, { pageSize: 100, format: 'mapped' }), + client.trackingRequests.list({}, { pageSize: 100, format: 'mapped' }), +]); + +// Calculate status distribution +const statusCounts = {}; +containers.forEach((container) => { + const status = container.currentStatus || 'unknown'; + statusCounts[status] = (statusCounts[status] || 0) + 1; +});`; + + return ( +
+ + Track Container + } + /> + +
+ {/* Error Banner */} + {'error' in data && data.error && ( +
+

Error loading dashboard data

+

{data.error}

+

+ Make sure T49_API_TOKEN is set in your environment variables. +

+
+ )} + + {/* Metric Cards */} +
+ + + + 0 ? 'warning' : 'default'} + /> +
+ + {/* Two Column Layout */} +
+ {/* Status Distribution */} + + + Status Distribution + + + {Object.keys(data.statusCounts).length > 0 ? ( +
+ {Object.entries(data.statusCounts) + .sort(([, a], [, b]) => (b as number) - (a as number)) + .map(([status, count]) => { + const config = containerStatusConfig[status]; + const percentage = Math.round( + ((count as number) / data.totalContainers) * 100 + ); + return ( +
+
+ +
+
+
+
+
+
+ + {count as number} + +
+ ); + })} +
+ ) : ( +

+ No containers tracked yet.{' '} + + Start tracking + +

+ )} + + + + {/* LFD Alerts */} + + + LFD Alerts + + + {data.urgentContainers.length > 0 ? ( +
+ {data.urgentContainers.map((container: any) => ( + +
+

+ {container.number} +

+

+ {container.terminal} +

+
+
+

+ {container.daysRemaining === 0 + ? 'Today' + : container.daysRemaining === 1 + ? 'Tomorrow' + : `${container.daysRemaining} days`} +

+

+ LFD: {formatDate(container.lfd)} +

+
+ + ))} + + View All → + +
+ ) : ( +

+ No urgent LFD alerts +

+ )} +
+
+
+ + {/* Quick Actions */} + + + Quick Actions + + +
+ + + + +
+
+
+ + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/search/page.tsx b/sdk-showcase/src/app/search/page.tsx new file mode 100644 index 00000000..f48a70bc --- /dev/null +++ b/sdk-showcase/src/app/search/page.tsx @@ -0,0 +1,296 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Global Search Page + * + * SDK Methods demonstrated: + * - client.search(query) - Search across shipments, containers, and tracking requests + */ + +interface SearchPageProps { + searchParams: Promise<{ q?: string }>; +} + +interface SearchResult { + id: string; + type?: string; + resourceType?: string; + resource_type?: string; + number?: string; + container_number?: string; + containerNumber?: string; + bill_of_lading?: string; + billOfLading?: string; + booking_number?: string; + bookingNumber?: string; + status?: string; + current_status?: string; + currentStatus?: string; + shipping_line?: { + name?: string; + scac?: string; + }; + shippingLine?: { + name?: string; + scac?: string; + }; + created_at?: string; + createdAt?: string; + updated_at?: string; + updatedAt?: string; +} + +async function performSearch(query: string) { + if (!hasApiToken()) { + return { + results: [], + error: 'T49_API_TOKEN environment variable is not set', + }; + } + + if (!query || query.trim().length < 2) { + return { results: [], error: null }; + } + + const client = getClient(); + + try { + const searchResult = await client.search(query.trim()); + const results = Array.isArray(searchResult) ? searchResult : searchResult?.items || []; + return { results }; + } catch (error) { + console.error('Search failed:', error); + return { + results: [], + error: error instanceof Error ? error.message : 'Search failed', + }; + } +} + +function getResultIcon(type: string): string { + const resultType = type?.toLowerCase() || ''; + if (resultType.includes('container')) return '📦'; + if (resultType.includes('shipment')) return '🚢'; + if (resultType.includes('tracking')) return '📍'; + return '📋'; +} + +function getResultLink(result: SearchResult): string { + const type = result.type || result.resourceType || result.resource_type || ''; + const typeLower = type.toLowerCase(); + + if (typeLower.includes('container')) { + return `/containers/${result.id}`; + } + if (typeLower.includes('shipment')) { + return `/shipments/${result.id}`; + } + if (typeLower.includes('tracking')) { + return `/tracking-requests/${result.id}`; + } + return '#'; +} + +function getResultTitle(result: SearchResult): string { + return ( + result.number || + result.container_number || + result.containerNumber || + result.bill_of_lading || + result.billOfLading || + result.booking_number || + result.bookingNumber || + result.id.slice(0, 8) + ); +} + +function formatResultType(type: string): string { + if (!type) return 'Unknown'; + return type + .replace(/_/g, ' ') + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export default async function SearchPage({ searchParams }: SearchPageProps) { + const { q: query } = await searchParams; + const { results, error } = query ? await performSearch(query) : { results: [], error: null }; + + const searchCode = `// Global search across all resources +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Search for containers, shipments, or tracking requests +const results = await client.search('${query || 'MSCU1234567'}'); + +// The search API accepts: +// - Container numbers (e.g., MSCU1234567) +// - Bill of lading numbers (e.g., MAEUSEA12345678) +// - Booking numbers +// - Reference numbers + +// Results include matches from: +// - Containers: by container number +// - Shipments: by BOL, booking, or reference numbers +// - Tracking Requests: by tracked number + +// Each result includes: +// - id: Resource ID for fetching details +// - type: Resource type (container, shipment, tracking_request) +// - number/bill_of_lading/booking_number: Matching identifier +// - status: Current status +// - shipping_line: Carrier information`; + + return ( +
+ + +
+ {/* Search Form */} + + +
+
+ +

+ Enter a container number, bill of lading, booking number, or reference number +

+
+ +
+
+
+ + {/* Error Banner */} + {error && ( +
+

Search error

+

{error}

+
+ )} + + {/* Search Results */} + {query && ( + + + + Search Results for "{query}" ({results.length}) + + + + {results.length > 0 ? ( +
+ {results.map((result: SearchResult) => { + const type = result.type || result.resourceType || result.resource_type || ''; + const status = result.status || result.current_status || result.currentStatus; + const carrier = result.shipping_line || result.shippingLine; + const updatedAt = result.updated_at || result.updatedAt; + + return ( + +
+ {getResultIcon(type)} +
+
+

{getResultTitle(result)}

+ + {formatResultType(type)} + +
+
+ {carrier?.name && 🚢 {carrier.name}} + {status && ( + + {status.replace(/_/g, ' ')} + + )} + {updatedAt && Updated: {formatDate(updatedAt)}} +
+
+
+ View → + + ); + })} +
+ ) : ( +
+

🔍

+

No results found for "{query}"

+

+ Try searching with a different container number, BOL, or booking number +

+
+ )} +
+
+ )} + + {/* Empty State */} + {!query && ( + + +
+

🔍

+

Search Your Shipments

+

+ Enter a container number, bill of lading, booking number, or reference number to + find your tracked shipments and containers. +

+
+ + Container: MSCU1234567 + + + BOL: MAEUSEA12345678 + + + Booking: 123456789 + +
+
+
+
+ )} + + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/shipments/[id]/page.tsx b/sdk-showcase/src/app/shipments/[id]/page.tsx new file mode 100644 index 00000000..2d8188d6 --- /dev/null +++ b/sdk-showcase/src/app/shipments/[id]/page.tsx @@ -0,0 +1,437 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button, StatusBadge } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { StopResumeButton } from './stop-resume-button'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Shipment Detail Page + * + * SDK Methods demonstrated: + * - client.shipments.get(id, includeContainers) - Get shipment with optional containers + * - client.shipments.update(id, attributes) - Update shipment metadata + * - client.shipments.stopTracking(id) - Stop tracking a shipment + * - client.shipments.resumeTracking(id) - Resume tracking a shipment + */ + +interface PageProps { + params: Promise<{ id: string }>; +} + +interface Container { + id: string; + number?: string; + // SDK mapped format uses nested structure + status?: string; + equipment?: { + type?: string; + length?: number; + height?: number; + weightLbs?: number; + }; + terminals?: { + podTerminal?: { + id: string; + name?: string; + nickname?: string; + firmsCode?: string; + } | null; + destinationTerminal?: { + id: string; + name?: string; + nickname?: string; + firmsCode?: string; + } | null; + }; + demurrage?: { + pickupLfd?: string; + pickupAppointmentAt?: string; + fees?: any[]; + holds?: any[]; + }; +} + +async function getShipment(id: string) { + if (!hasApiToken()) { + return { + shipment: null, + error: 'T49_API_TOKEN environment variable is not set', + }; + } + + const client = getClient(); + + try { + // Get shipment with containers included + const result = await client.shipments.get(id, true, { format: 'mapped' }); + return { shipment: result }; + } catch (error) { + console.error('Failed to fetch shipment:', error); + return { + shipment: null, + error: error instanceof Error ? error.message : 'Failed to fetch shipment', + }; + } +} + +function getStatusColor(status: string): string { + switch (status) { + case 'in_transit': + case 'active': + return 'bg-blue-100 text-blue-800'; + case 'arrived': + return 'bg-green-100 text-green-800'; + case 'delivered': + return 'bg-emerald-100 text-emerald-800'; + case 'pending': + return 'bg-yellow-100 text-yellow-800'; + default: + return 'bg-gray-100 text-gray-800'; + } +} + +function formatStatus(status: string): string { + return status + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export default async function ShipmentDetailPage({ params }: PageProps) { + const { id } = await params; + const { shipment, error } = await getShipment(id); + + if (!shipment && !error) { + notFound(); + } + + const detailCode = `// Get shipment with containers +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get shipment details with containers included +const shipment = await client.shipments.get('${id}', true, { + format: 'mapped' +}); + +// Update shipment metadata +await client.shipments.update('${id}', { + ref_numbers: ['PO-123456', 'INV-789'], + tags: ['priority', 'customer-abc'] +}); + +// Stop tracking (if shipment is complete or no longer needed) +await client.shipments.stopTracking('${id}'); + +// Resume tracking (if needed again) +await client.shipments.resumeTracking('${id}');`; + + // Extract shipment fields from SDK mapped format + const bol = shipment?.billOfLading; + const status = shipment?.status || 'unknown'; + // SDK returns shippingLineScac (SCAC code only, not a name) + const carrierScac = shipment?.shippingLineScac; + const carrierName = carrierScac; // SDK doesn't include carrier name in mapped format + // Vessel info from SDK mapper + const vesselName = shipment?.vesselAtPod?.name; + const voyageNumber = shipment?.vesselAtPod?.voyageNumber; + // Port info from SDK nested ports structure + const polName = shipment?.ports?.portOfLading?.name; + const polCode = shipment?.ports?.portOfLading?.locode; + const podName = shipment?.ports?.portOfDischarge?.name; + const podCode = shipment?.ports?.portOfDischarge?.locode; + const etd = shipment?.ports?.portOfLading?.etd; + const atd = shipment?.ports?.portOfLading?.atd; + const eta = shipment?.ports?.portOfDischarge?.eta; + const ata = shipment?.ports?.portOfDischarge?.ata; + // Tracking info from SDK mapper + const trackingStatus = shipment?.tracking?.lineTrackingStoppedAt ? 'stopped' : 'active'; + const containers = shipment?.containers || []; + const refNumbers = shipment?.refNumbers || []; + const tags = shipment?.tags || []; + const customer = shipment?.customerName; + + return ( +
+ + + +
+ } + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading shipment

+

{error}

+
+ )} + + {shipment && ( + <> + {/* Tracking Status Banner */} + {trackingStatus === 'stopped' && ( +
+

Tracking Stopped

+

+ This shipment is no longer being tracked. Click "Resume Tracking" to restart. +

+
+ )} + + {/* Main Info */} + + + Shipment Details + + +
+
+

Bill of Lading

+

{bol || '—'}

+
+
+

Shipment ID

+

{id.slice(0, 12)}...

+
+
+

Status

+ + {formatStatus(status)} + +
+
+

Tracking

+ + {trackingStatus === 'active' ? 'Active' : 'Stopped'} + +
+
+

Carrier

+

{carrierName || '—'}

+ {carrierScac &&

SCAC: {carrierScac}

} +
+
+

Vessel

+

{vesselName || '—'}

+ {voyageNumber &&

Voyage: {voyageNumber}

} +
+
+

Customer

+

{customer || '—'}

+
+
+

Containers

+

{containers.length}

+
+
+
+
+ + {/* Route Info */} + + + Route + + +
+ {/* Origin */} +
+

Port of Loading

+

{polName || '—'}

+ {polCode &&

{polCode}

} +
+
+ ETD: + {etd ? formatDate(etd) : '—'} +
+
+ ATD: + {atd ? formatDate(atd) : '—'} +
+
+
+ + {/* Arrow */} +
+
+
+
+
+ + {/* Destination */} +
+

Port of Discharge

+

{podName || '—'}

+ {podCode &&

{podCode}

} +
+
+ ETA: + {eta ? formatDate(eta) : '—'} +
+
+ ATA: + {ata ? formatDate(ata) : '—'} +
+
+
+
+ + + + {/* Containers */} + + + Containers ({containers.length}) + + + {containers.length > 0 ? ( +
+ + + + + + + + + + + + + {containers.map((container: Container) => { + // SDK mapped format uses nested structures + const containerStatus = container.status || 'unknown'; + const lfd = container.demurrage?.pickupLfd; + const terminal = container.terminals?.podTerminal?.name || '—'; + const equipment = container.equipment?.type || '—'; + + return ( + + + + + + + + + ); + })} + +
NumberEquipmentStatusTerminalLFD
+ + {container.number} + + {equipment} + + {terminal}{lfd ? formatDate(lfd) : '—'} + + Details → + +
+
+ ) : ( +

No containers found

+ )} +
+
+ + {/* Reference Numbers & Tags */} +
+ + + Reference Numbers + + + {refNumbers.length > 0 ? ( +
+ {refNumbers.map((ref: string, index: number) => ( + + {ref} + + ))} +
+ ) : ( +

No reference numbers

+ )} +
+
+ + + + Tags + + + {tags.length > 0 ? ( +
+ {tags.map((tag: string, index: number) => ( + + {tag} + + ))} +
+ ) : ( +

No tags

+ )} +
+
+
+ + {/* Raw Data */} + + + Raw API Response + + +
+                  {JSON.stringify(shipment, null, 2)}
+                
+
+
+ + )} + + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/shipments/[id]/stop-resume-button.tsx b/sdk-showcase/src/app/shipments/[id]/stop-resume-button.tsx new file mode 100644 index 00000000..8eed79b9 --- /dev/null +++ b/sdk-showcase/src/app/shipments/[id]/stop-resume-button.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Button } from '@/components/features'; + +interface StopResumeButtonProps { + shipmentId: string; + currentStatus: string; +} + +export function StopResumeButton({ shipmentId, currentStatus }: StopResumeButtonProps) { + const [loading, setLoading] = useState(false); + const router = useRouter(); + const isActive = currentStatus === 'active'; + + async function handleClick() { + setLoading(true); + + try { + const endpoint = isActive + ? `/api/shipments/${shipmentId}/stop` + : `/api/shipments/${shipmentId}/resume`; + + const response = await fetch(endpoint, { method: 'POST' }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Operation failed'); + } + + // Refresh the page to show updated status + router.refresh(); + } catch (error) { + console.error('Stop/resume error:', error); + alert(error instanceof Error ? error.message : 'Operation failed'); + } finally { + setLoading(false); + } + } + + return ( + + ); +} diff --git a/sdk-showcase/src/app/shipments/page.tsx b/sdk-showcase/src/app/shipments/page.tsx new file mode 100644 index 00000000..0d362d59 --- /dev/null +++ b/sdk-showcase/src/app/shipments/page.tsx @@ -0,0 +1,310 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button, StatusBadge } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; + +// Force dynamic rendering - we fetch live data from the API +export const dynamic = 'force-dynamic'; + +/** + * Shipments List Page + * + * SDK Methods demonstrated: + * - client.shipments.list(filters, options) - List and filter shipments + */ + +interface Shipment { + id: string; + // From SDK mapped format + billOfLading?: string; + shippingLineScac?: string; + customerName?: string; + status?: string; + // Nested ports structure from SDK mapper + ports?: { + portOfLading?: { + name?: string; + locode?: string; + code?: string; + countryCode?: string; + etd?: string; + atd?: string; + timezone?: string; + } | null; + portOfDischarge?: { + name?: string; + locode?: string; + code?: string; + countryCode?: string; + eta?: string; + ata?: string; + originalEta?: string; + timezone?: string; + terminal?: { + id: string; + name?: string; + nickname?: string; + firmsCode?: string; + } | null; + } | null; + destination?: { + locode?: string; + name?: string; + eta?: string; + ata?: string; + timezone?: string; + terminal?: { + id: string; + name?: string; + nickname?: string; + firmsCode?: string; + } | null; + } | null; + }; + // Vessel info from SDK mapper + vesselAtPod?: { + name?: string; + imo?: string; + voyageNumber?: string; + }; + // Containers array from SDK mapper + containers?: Array<{ id: string; number?: string }>; +} + +async function getShipments() { + if (!hasApiToken()) { + return { + shipments: [], + error: 'T49_API_TOKEN environment variable is not set. Get your API key from https://app.terminal49.com/settings/api', + }; + } + + const client = getClient(); + + try { + const result = await client.shipments.list({}, { pageSize: 50, format: 'mapped' }); + // SDK returns { items: [], links: ..., meta: ... } for mapped format + const shipments = Array.isArray(result) ? result : result?.items || []; + + return { shipments }; + } catch (error) { + console.error('Failed to fetch shipments:', error); + return { + shipments: [], + error: error instanceof Error ? error.message : 'Failed to fetch shipments', + }; + } +} + +function getShipmentStatusColor(status: string): string { + switch (status) { + case 'in_transit': + case 'active': + return 'bg-blue-100 text-blue-800'; + case 'arrived': + return 'bg-green-100 text-green-800'; + case 'delivered': + return 'bg-emerald-100 text-emerald-800'; + case 'pending': + return 'bg-yellow-100 text-yellow-800'; + default: + return 'bg-gray-100 text-gray-800'; + } +} + +function formatStatus(status: string): string { + return status + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export default async function ShipmentsPage() { + const { shipments, error } = await getShipments(); + + const listCode = `// List shipments with filters and pagination +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get all shipments +const shipments = await client.shipments.list( + {}, // filters (optional) + { pageSize: 50, format: 'mapped' } +); + +// Filter by status +const activeShipments = await client.shipments.list( + { status: 'in_transit' }, + { format: 'mapped' } +); + +// Filter by port or carrier +const laShipments = await client.shipments.list( + { pod: 'USLAX', carrier: 'MSCU' }, + { format: 'mapped' } +); + +// Get shipments updated after a date +const recentShipments = await client.shipments.list( + { updated_after: '2024-01-01T00:00:00Z' }, + { format: 'mapped' } +);`; + + // Calculate stats + const inTransitCount = shipments.filter( + (s: Shipment) => s.status === 'in_transit' || s.status === 'active' + ).length; + const arrivedCount = shipments.filter((s: Shipment) => s.status === 'arrived').length; + const deliveredCount = shipments.filter((s: Shipment) => s.status === 'delivered').length; + + return ( +
+ + Track New} + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading shipments

+

{error}

+
+ )} + + {/* Stats Summary */} +
+ + +

{shipments.length}

+

Total Shipments

+
+
+ + +

{inTransitCount}

+

In Transit

+
+
+ + +

{arrivedCount}

+

Arrived

+
+
+ + +

{deliveredCount}

+

Delivered

+
+
+
+ + {/* Shipments Table */} + + + All Shipments + + + {shipments.length > 0 ? ( +
+ + + + + + + + + + + + + + {shipments.map((shipment: Shipment) => { + const bol = shipment.billOfLading; + const status = shipment.status || 'unknown'; + // SDK returns shippingLineScac (just the SCAC code, no name) + const carrier = shipment.shippingLineScac || '—'; + // SDK returns nested ports structure + const pol = shipment.ports?.portOfLading?.name || '—'; + const pod = shipment.ports?.portOfDischarge?.name || '—'; + const eta = shipment.ports?.portOfDischarge?.eta; + // SDK returns containers array + const containerCount = shipment.containers?.length || 0; + + return ( + + + + + + + + + + ); + })} + +
BOL / BookingCarrierRouteStatusETAContainers
+ + {bol || shipment.id.slice(0, 8)} + + + {carrier} + + + {pol} → {pod} + + + + {formatStatus(status)} + + + + {eta ? formatDate(eta) : '—'} + + + {containerCount} + + + Details → + +
+
+ ) : ( +
+

No shipments found.

+ + Start tracking a container + +
+ )} +
+
+ + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/shipping-lines/page.tsx b/sdk-showcase/src/app/shipping-lines/page.tsx new file mode 100644 index 00000000..744ec8ce --- /dev/null +++ b/sdk-showcase/src/app/shipping-lines/page.tsx @@ -0,0 +1,207 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Shipping Lines Browser Page + * + * SDK Methods demonstrated: + * - client.shippingLines.list() - List all supported shipping lines + */ + +interface ShippingLine { + id: string; + name?: string; + scac?: string; + short_name?: string; + shortName?: string; + tracking_supported?: boolean; + trackingSupported?: boolean; + bol_tracking_supported?: boolean; + bolTrackingSupported?: boolean; + booking_tracking_supported?: boolean; + bookingTrackingSupported?: boolean; + container_tracking_supported?: boolean; + containerTrackingSupported?: boolean; + mbol_tracking_supported?: boolean; + mbolTrackingSupported?: boolean; +} + +async function getShippingLines() { + if (!hasApiToken()) { + return { + shippingLines: [], + error: 'T49_API_TOKEN environment variable is not set. Get your API key from https://app.terminal49.com/settings/api', + }; + } + + const client = getClient(); + + try { + const result = await client.shippingLines.list(undefined, { format: 'mapped' }); + const shippingLines = Array.isArray(result) ? result : result?.items || []; + + return { shippingLines }; + } catch (error) { + console.error('Failed to fetch shipping lines:', error); + return { + shippingLines: [], + error: error instanceof Error ? error.message : 'Failed to fetch shipping lines', + }; + } +} + +export default async function ShippingLinesPage() { + const { shippingLines, error } = await getShippingLines(); + + const listCode = `// List all supported shipping lines +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get all shipping lines +const shippingLines = await client.shippingLines.list({ + format: 'mapped' +}); + +// Each shipping line includes: +// - name: Full carrier name (e.g., "Maersk Line") +// - scac: Standard Carrier Alpha Code (e.g., "MAEU") +// - tracking_supported: Whether tracking is available +// - bol_tracking_supported: Bill of Lading tracking +// - booking_tracking_supported: Booking number tracking +// - container_tracking_supported: Container number tracking`; + + // Count carriers with different tracking capabilities + const withBolTracking = shippingLines.filter( + (s: ShippingLine) => s.bol_tracking_supported || s.bolTrackingSupported + ).length; + const withBookingTracking = shippingLines.filter( + (s: ShippingLine) => s.booking_tracking_supported || s.bookingTrackingSupported + ).length; + const withContainerTracking = shippingLines.filter( + (s: ShippingLine) => s.container_tracking_supported || s.containerTrackingSupported + ).length; + + return ( +
+ + +
+ {/* Error Banner */} + {error && ( +
+

Error loading shipping lines

+

{error}

+
+ )} + + {/* Stats Summary */} +
+ + +

{shippingLines.length}

+

Total Carriers

+
+
+ + +

{withContainerTracking}

+

Container Tracking

+
+
+ + +

{withBolTracking}

+

BOL Tracking

+
+
+ + +

{withBookingTracking}

+

Booking Tracking

+
+
+
+ + {/* Shipping Lines Grid */} + + + All Supported Carriers + + + {shippingLines.length > 0 ? ( +
+ {shippingLines + .sort((a: ShippingLine, b: ShippingLine) => + (a.name || '').localeCompare(b.name || '') + ) + .map((line: ShippingLine) => { + const name = line.name; + const scac = line.scac; + const shortName = line.short_name || line.shortName; + const bolSupported = line.bol_tracking_supported || line.bolTrackingSupported; + const bookingSupported = line.booking_tracking_supported || line.bookingTrackingSupported; + const containerSupported = line.container_tracking_supported || line.containerTrackingSupported; + const mbolSupported = line.mbol_tracking_supported || line.mbolTrackingSupported; + + return ( +
+
+
+

{name}

+ {shortName && shortName !== name && ( +

{shortName}

+ )} +
+ + {scac} + +
+ +
+ {containerSupported && ( + + Container + + )} + {bolSupported && ( + + BOL + + )} + {bookingSupported && ( + + Booking + + )} + {mbolSupported && ( + + Master BOL + + )} +
+
+ ); + })} +
+ ) : ( +

No shipping lines found

+ )} +
+
+ + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/tracking-requests/[id]/page.tsx b/sdk-showcase/src/app/tracking-requests/[id]/page.tsx new file mode 100644 index 00000000..0cc2148a --- /dev/null +++ b/sdk-showcase/src/app/tracking-requests/[id]/page.tsx @@ -0,0 +1,262 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; + +// Force dynamic rendering +export const dynamic = 'force-dynamic'; + +/** + * Tracking Request Detail Page + * + * SDK Methods demonstrated: + * - client.trackingRequests.get(id) - Get tracking request details + * - client.trackingRequests.update(id, attributes) - Update tracking request + */ + +interface PageProps { + params: Promise<{ id: string }>; +} + +async function getTrackingRequest(id: string) { + if (!hasApiToken()) { + return { + trackingRequest: null, + error: 'T49_API_TOKEN environment variable is not set', + }; + } + + const client = getClient(); + + try { + const result = await client.trackingRequests.get(id, { format: 'mapped' }); + return { trackingRequest: result }; + } catch (error) { + console.error('Failed to fetch tracking request:', error); + return { + trackingRequest: null, + error: error instanceof Error ? error.message : 'Failed to fetch tracking request', + }; + } +} + +function formatStatus(status: string): string { + return status + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export default async function TrackingRequestDetailPage({ params }: PageProps) { + const { id } = await params; + const { trackingRequest, error } = await getTrackingRequest(id); + + if (!trackingRequest && !error) { + notFound(); + } + + const detailCode = `// Get tracking request details +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +const trackingRequest = await client.trackingRequests.get('${id}', { + format: 'mapped' +}); + +// Update tracking request (e.g., reference numbers) +await client.trackingRequests.update('${id}', { + ref_numbers: ['PO-123456', 'INV-789'], + tags: ['priority', 'customer-abc'] +});`; + + // Extract fields with fallbacks for different formats + const number = trackingRequest?.request_number || trackingRequest?.requestNumber; + const type = trackingRequest?.request_type || trackingRequest?.requestType || 'unknown'; + const status = trackingRequest?.request_status || trackingRequest?.requestStatus || 'unknown'; + const scac = trackingRequest?.scac; + const carrierName = trackingRequest?.shipping_line?.name || trackingRequest?.shippingLine?.name; + const shipmentId = trackingRequest?.shipment?.id; + const createdAt = trackingRequest?.created_at || trackingRequest?.createdAt; + const updatedAt = trackingRequest?.updated_at || trackingRequest?.updatedAt; + const failedReason = trackingRequest?.failed_reason || trackingRequest?.failedReason; + const refNumbers = trackingRequest?.ref_numbers || trackingRequest?.refNumbers || []; + const tags = trackingRequest?.tags || []; + + return ( +
+ + {shipmentId && ( + + )} + +
+ } + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading tracking request

+

{error}

+
+ )} + + {trackingRequest && ( + <> + {/* Status Banner */} + {status === 'failed' && failedReason && ( +
+

Tracking Request Failed

+

{failedReason}

+
+ )} + + {/* Main Info */} + + + Request Details + + +
+
+

Tracking Number

+

{number || '—'}

+
+
+

Request Type

+

{type.replace('_', ' ')}

+
+
+

Status

+ + {formatStatus(status)} + +
+
+

Carrier

+

{carrierName || scac || '—'}

+ {scac && carrierName && ( +

SCAC: {scac}

+ )} +
+
+

Created

+

{createdAt ? formatDate(createdAt) : '—'}

+
+
+

Last Updated

+

{updatedAt ? formatDate(updatedAt) : '—'}

+
+
+
+
+ + {/* Shipment Link */} + {shipmentId && ( + + + Associated Shipment + + +
+
+

Shipment

+

{shipmentId}

+
+ + View Shipment → + +
+
+
+ )} + + {/* Reference Numbers & Tags */} +
+ + + Reference Numbers + + + {refNumbers.length > 0 ? ( +
+ {refNumbers.map((ref: string, index: number) => ( + + {ref} + + ))} +
+ ) : ( +

No reference numbers

+ )} +
+
+ + + + Tags + + + {tags.length > 0 ? ( +
+ {tags.map((tag: string, index: number) => ( + + {tag} + + ))} +
+ ) : ( +

No tags

+ )} +
+
+
+ + {/* Raw Data */} + + + Raw API Response + + +
+                  {JSON.stringify(trackingRequest, null, 2)}
+                
+
+
+ + )} + + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/tracking-requests/new/page.tsx b/sdk-showcase/src/app/tracking-requests/new/page.tsx new file mode 100644 index 00000000..a6d77a1e --- /dev/null +++ b/sdk-showcase/src/app/tracking-requests/new/page.tsx @@ -0,0 +1,420 @@ +'use client'; + +import { useState } from 'react'; +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import Link from 'next/link'; + +/** + * Create Tracking Request Page + * + * SDK Methods demonstrated: + * - client.trackingRequests.inferNumber(number) - Auto-detect number type and carrier + * - client.trackingRequests.createFromInfer(number, options) - Create from inference result + * - client.trackingRequests.create(params) - Create with explicit parameters + */ + +type Step = 'enter' | 'infer' | 'select' | 'result'; + +interface Candidate { + scac: string; + name: string; + confidence: number; +} + +interface InferenceResult { + number_type?: string; + numberType?: string; + decision?: string; + candidates?: Candidate[]; + selected?: { scac: string; name: string }; + shipping_line?: { + decision?: string; + candidates?: Candidate[]; + selected?: { scac: string; name: string }; + }; + shippingLine?: { + decision?: string; + candidates?: Candidate[]; + selected?: { scac: string; name: string }; + }; +} + +interface CreateResult { + success: boolean; + trackingRequest?: any; + shipment?: any; + error?: string; +} + +export default function NewTrackingRequestPage() { + const [step, setStep] = useState('enter'); + const [trackingNumber, setTrackingNumber] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [inferResult, setInferResult] = useState(null); + const [selectedScac, setSelectedScac] = useState(null); + const [createResult, setCreateResult] = useState(null); + + async function handleInfer() { + if (!trackingNumber.trim()) { + setError('Please enter a tracking number'); + return; + } + + setLoading(true); + setError(null); + + try { + const response = await fetch('/api/tracking-requests/infer', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ number: trackingNumber.trim() }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to infer tracking number'); + } + + setInferResult(data); + + // Check if we need carrier selection + const shippingLine = data.shipping_line || data.shippingLine; + const decision = shippingLine?.decision || data.decision; + const selected = shippingLine?.selected || data.selected; + + if (decision === 'auto_select' && selected?.scac) { + setSelectedScac(selected.scac); + } + + setStep('infer'); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + } + + async function handleCreate() { + setLoading(true); + setError(null); + + try { + const response = await fetch('/api/tracking-requests/create', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + number: trackingNumber.trim(), + scac: selectedScac, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to create tracking request'); + } + + setCreateResult(data); + setStep('result'); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + } + + function reset() { + setStep('enter'); + setTrackingNumber(''); + setInferResult(null); + setSelectedScac(null); + setCreateResult(null); + setError(null); + } + + const inferCode = `// Step 1: Infer the number type and carrier +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +const inference = await client.trackingRequests.inferNumber('${trackingNumber || 'MSCU1234567'}'); +// Returns: +// { +// number_type: 'container' | 'booking' | 'bill_of_lading', +// decision: 'auto_select' | 'needs_confirmation', +// candidates: [{ scac: 'MSCU', name: 'MSC', confidence: 0.95 }, ...], +// selected: { scac: 'MSCU', name: 'MSC' } // if auto_select +// }`; + + const createCode = `// Step 2: Create tracking request based on inference +${ + selectedScac + ? `// Using inferred carrier: ${selectedScac}` + : '// Carrier will be auto-detected or specified' +} +const result = await client.trackingRequests.createFromInfer( + '${trackingNumber || 'MSCU1234567'}', + ${selectedScac ? `{ scac: '${selectedScac}' }` : '{}'} +); + +// Alternative: Create with explicit parameters +const request = await client.trackingRequests.create({ + request_type: 'container', + request_number: '${trackingNumber || 'MSCU1234567'}', + scac: '${selectedScac || 'MSCU'}', +});`; + + const shippingLine = inferResult?.shipping_line || inferResult?.shippingLine; + const candidates = shippingLine?.candidates || inferResult?.candidates || []; + const decision = shippingLine?.decision || inferResult?.decision; + const numberType = inferResult?.number_type || inferResult?.numberType; + + return ( +
+ + +
+ {/* Progress Steps */} +
+ {[ + { key: 'enter', label: 'Enter Number' }, + { key: 'infer', label: 'Auto-Detect' }, + { key: 'select', label: 'Confirm' }, + { key: 'result', label: 'Complete' }, + ].map((s, index) => { + const isActive = s.key === step; + const isPast = + ['enter', 'infer', 'select', 'result'].indexOf(step) > + ['enter', 'infer', 'select', 'result'].indexOf(s.key); + + return ( +
+
+ {isPast ? '✓' : index + 1} +
+ + {s.label} + + {index < 3 && ( +
+ )} +
+ ); + })} +
+ + {/* Error Banner */} + {error && ( +
+

Error

+

{error}

+
+ )} + + {/* Step 1: Enter Number */} + {step === 'enter' && ( + + + Enter Tracking Number + + +
+
+ + setTrackingNumber(e.target.value.toUpperCase())} + placeholder="MSCU1234567" + className="w-full px-4 py-2 rounded-lg bg-kumo-recessed border border-kumo-line focus:border-kumo-focus focus:ring-1 focus:ring-kumo-focus outline-none font-mono text-lg" + onKeyDown={(e) => e.key === 'Enter' && handleInfer()} + /> +

+ Container number, booking number, or bill of lading +

+
+ +
+
+
+ )} + + {/* Step 2: Infer Results */} + {step === 'infer' && inferResult && ( + + + Auto-Detection Results + + +
+ {/* Detected Type */} +
+

Detected Type

+

+ {numberType?.replace('_', ' ') || 'Unknown'} +

+
+ + {/* Carrier Selection */} +
+

+ {decision === 'auto_select' + ? 'Carrier Detected' + : 'Select Carrier'} +

+ {candidates.length > 0 ? ( +
+ {candidates.map((candidate: Candidate) => ( + + ))} +
+ ) : ( +

+ No carrier candidates found. Please verify the tracking number. +

+ )} +
+ +
+ + +
+
+
+
+ )} + + {/* Step 3: Result */} + {step === 'result' && createResult && ( + + + + {createResult.success ? '✅ Tracking Request Created' : '❌ Failed'} + + + + {createResult.success ? ( +
+
+

+ Successfully created tracking request for {trackingNumber} +

+ {createResult.shipment?.id && ( +

+ Shipment ID: {createResult.shipment.id} +

+ )} +
+ +
+ {createResult.shipment?.id && ( + + )} + {createResult.trackingRequest?.id && ( + + )} + +
+
+ ) : ( +
+
+

+ {createResult.error || 'Failed to create tracking request'} +

+
+ +
+ )} +
+
+ )} + + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/tracking-requests/page.tsx b/sdk-showcase/src/app/tracking-requests/page.tsx new file mode 100644 index 00000000..5b709e34 --- /dev/null +++ b/sdk-showcase/src/app/tracking-requests/page.tsx @@ -0,0 +1,300 @@ +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button, StatusBadge } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; +import { getClient, hasApiToken } from '@/lib/terminal49'; +import { formatDate } from '@/lib/utils'; +import Link from 'next/link'; + +// Force dynamic rendering - we fetch live data from the API +export const dynamic = 'force-dynamic'; + +/** + * Tracking Requests List Page + * + * SDK Methods demonstrated: + * - client.trackingRequests.list(filters, options) - List and filter tracking requests + */ + +interface TrackingRequest { + id: string; + request_number?: string; + requestNumber?: string; + request_type?: string; + requestType?: string; + request_status?: string; + requestStatus?: string; + scac?: string; + shipping_line?: { name?: string; scac?: string }; + shippingLine?: { name?: string; scac?: string }; + shipment?: { id?: string }; + created_at?: string; + createdAt?: string; + failed_reason?: string; + failedReason?: string; +} + +async function getTrackingRequests() { + if (!hasApiToken()) { + return { + trackingRequests: [], + error: 'T49_API_TOKEN environment variable is not set. Get your API key from https://app.terminal49.com/settings/api', + }; + } + + const client = getClient(); + + try { + const result = await client.trackingRequests.list({}, { pageSize: 50, format: 'mapped' }); + // SDK returns { items: [], links: ..., meta: ... } for mapped format + const trackingRequests = Array.isArray(result) ? result : result?.items || []; + + return { trackingRequests }; + } catch (error) { + console.error('Failed to fetch tracking requests:', error); + return { + trackingRequests: [], + error: error instanceof Error ? error.message : 'Failed to fetch tracking requests', + }; + } +} + +function getStatusColor(status: string): 'default' | 'success' | 'warning' | 'error' { + switch (status) { + case 'created': + case 'succeeded': + return 'success'; + case 'pending': + case 'processing': + return 'warning'; + case 'failed': + return 'error'; + default: + return 'default'; + } +} + +function formatStatus(status: string): string { + return status + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +export default async function TrackingRequestsPage() { + const { trackingRequests, error } = await getTrackingRequests(); + + const listCode = `// List tracking requests with filters and pagination +const client = new Terminal49Client({ apiToken: process.env.T49_API_TOKEN }); + +// Get all tracking requests +const trackingRequests = await client.trackingRequests.list( + {}, // filters (optional) + { pageSize: 50, format: 'mapped' } +); + +// Filter by status +const pendingRequests = await client.trackingRequests.list( + { status: 'pending' }, + { format: 'mapped' } +); + +// Filter by request type +const containerRequests = await client.trackingRequests.list( + { request_type: 'container' }, + { format: 'mapped' } +);`; + + return ( +
+ + New Request} + /> + +
+ {/* Error Banner */} + {error && ( +
+

Error loading tracking requests

+

{error}

+
+ )} + + {/* Stats Summary */} +
+ + +

+ {trackingRequests.length} +

+

Total Requests

+
+
+ + +

+ { + trackingRequests.filter( + (r: TrackingRequest) => + r.request_status === 'succeeded' || r.requestStatus === 'succeeded' + ).length + } +

+

Succeeded

+
+
+ + +

+ { + trackingRequests.filter( + (r: TrackingRequest) => + r.request_status === 'pending' || r.requestStatus === 'pending' + ).length + } +

+

Pending

+
+
+ + +

+ { + trackingRequests.filter( + (r: TrackingRequest) => + r.request_status === 'failed' || r.requestStatus === 'failed' + ).length + } +

+

Failed

+
+
+
+ + {/* Tracking Requests Table */} + + + All Tracking Requests + + + {trackingRequests.length > 0 ? ( +
+ + + + + + + + + + + + + + {trackingRequests.map((request: TrackingRequest) => { + const number = request.request_number || request.requestNumber; + const type = request.request_type || request.requestType || 'unknown'; + const status = request.request_status || request.requestStatus || 'unknown'; + const carrier = + request.shipping_line?.name || + request.shippingLine?.name || + request.scac || + '—'; + const shipmentId = request.shipment?.id; + const createdAt = request.created_at || request.createdAt; + const failedReason = request.failed_reason || request.failedReason; + + return ( + + + + + + + + + + ); + })} + +
NumberTypeCarrierStatusShipmentCreated
+ + {number} + + + {type.replace('_', ' ')} + + {carrier} + +
+ + {formatStatus(status)} + + {failedReason && ( + + {failedReason} + + )} +
+
+ {shipmentId ? ( + + View + + ) : ( + + )} + + + {createdAt ? formatDate(createdAt) : '—'} + + + + Details → + +
+
+ ) : ( +
+

No tracking requests found.

+ + Create your first tracking request + +
+ )} +
+
+ + {/* SDK Code Panel */} + +
+
+ ); +} diff --git a/sdk-showcase/src/app/webhooks/page.tsx b/sdk-showcase/src/app/webhooks/page.tsx new file mode 100644 index 00000000..046cbf16 --- /dev/null +++ b/sdk-showcase/src/app/webhooks/page.tsx @@ -0,0 +1,508 @@ +'use client'; + +import { useState } from 'react'; +import { PageHeader } from '@/components/layout'; +import { Card, CardHeader, CardTitle, CardContent, Button } from '@/components/features'; +import { CodePanel } from '@/components/code-panel'; + +/** + * Webhooks Simulator Page + * + * This page demonstrates webhook event structures and handling patterns. + * It simulates webhook payloads without requiring actual webhook setup. + * + * Note: The Terminal49 SDK does not include webhook CRUD methods. + * Webhook management is done through the Terminal49 web dashboard. + */ + +type WebhookEventType = + | 'shipment.estimated.arrival' + | 'shipment.estimated.departure' + | 'shipment.transport.arrival' + | 'shipment.transport.departure' + | 'container.transport.vessel_loaded' + | 'container.transport.vessel_departed' + | 'container.transport.vessel_arrived' + | 'container.transport.vessel_discharged' + | 'container.transport.full_out' + | 'container.transport.empty_returned' + | 'container.transport.transshipment_arrived' + | 'container.transport.transshipment_departed' + | 'container.availability.changed' + | 'container.lfd.updated' + | 'container.hold.added' + | 'container.hold.released' + | 'tracking_request.created' + | 'tracking_request.failed'; + +interface EventExample { + type: WebhookEventType; + label: string; + description: string; + category: string; + payload: object; +} + +const webhookEvents: EventExample[] = [ + { + type: 'shipment.estimated.arrival', + label: 'ETA Updated', + description: 'Triggered when the estimated time of arrival changes', + category: 'Shipment', + payload: { + id: 'evt_abc123', + type: 'shipment.estimated.arrival', + created_at: '2025-02-08T10:30:00Z', + data: { + shipment_id: 'ship_def456', + bill_of_lading: 'MAEUSEA12345678', + pod_eta_at: '2025-02-15T08:00:00Z', + previous_pod_eta_at: '2025-02-14T08:00:00Z', + change_reason: 'vessel_delay', + }, + }, + }, + { + type: 'shipment.transport.arrival', + label: 'Vessel Arrived', + description: 'Triggered when the vessel arrives at port of discharge', + category: 'Shipment', + payload: { + id: 'evt_abc124', + type: 'shipment.transport.arrival', + created_at: '2025-02-08T14:30:00Z', + data: { + shipment_id: 'ship_def456', + bill_of_lading: 'MAEUSEA12345678', + pod_ata_at: '2025-02-08T14:30:00Z', + port_name: 'Los Angeles', + port_locode: 'USLAX', + }, + }, + }, + { + type: 'container.transport.vessel_discharged', + label: 'Container Discharged', + description: 'Triggered when a container is unloaded from the vessel', + category: 'Container', + payload: { + id: 'evt_abc125', + type: 'container.transport.vessel_discharged', + created_at: '2025-02-08T18:45:00Z', + data: { + container_id: 'cont_ghi789', + container_number: 'MSCU1234567', + shipment_id: 'ship_def456', + discharged_at: '2025-02-08T18:45:00Z', + terminal_name: 'APM Los Angeles', + terminal_firms_code: 'Y258', + }, + }, + }, + { + type: 'container.availability.changed', + label: 'Availability Changed', + description: 'Triggered when container availability status changes', + category: 'Container', + payload: { + id: 'evt_abc126', + type: 'container.availability.changed', + created_at: '2025-02-09T09:00:00Z', + data: { + container_id: 'cont_ghi789', + container_number: 'MSCU1234567', + available_for_pickup: true, + available_for_pickup_at: '2025-02-09T09:00:00Z', + previous_available_for_pickup: false, + }, + }, + }, + { + type: 'container.lfd.updated', + label: 'LFD Updated', + description: 'Triggered when the Last Free Day changes', + category: 'Container', + payload: { + id: 'evt_abc127', + type: 'container.lfd.updated', + created_at: '2025-02-09T10:00:00Z', + data: { + container_id: 'cont_ghi789', + container_number: 'MSCU1234567', + pickup_lfd: '2025-02-12', + previous_pickup_lfd: '2025-02-10', + terminal_name: 'APM Los Angeles', + }, + }, + }, + { + type: 'container.hold.added', + label: 'Hold Added', + description: 'Triggered when a hold is placed on a container', + category: 'Container', + payload: { + id: 'evt_abc128', + type: 'container.hold.added', + created_at: '2025-02-08T20:00:00Z', + data: { + container_id: 'cont_ghi789', + container_number: 'MSCU1234567', + hold_type: 'customs', + hold_status: 'active', + description: 'Customs examination required', + }, + }, + }, + { + type: 'container.hold.released', + label: 'Hold Released', + description: 'Triggered when a hold is released from a container', + category: 'Container', + payload: { + id: 'evt_abc129', + type: 'container.hold.released', + created_at: '2025-02-09T11:00:00Z', + data: { + container_id: 'cont_ghi789', + container_number: 'MSCU1234567', + hold_type: 'customs', + hold_status: 'released', + released_at: '2025-02-09T11:00:00Z', + }, + }, + }, + { + type: 'container.transport.full_out', + label: 'Container Picked Up', + description: 'Triggered when a full container leaves the terminal', + category: 'Container', + payload: { + id: 'evt_abc130', + type: 'container.transport.full_out', + created_at: '2025-02-10T07:30:00Z', + data: { + container_id: 'cont_ghi789', + container_number: 'MSCU1234567', + full_out_at: '2025-02-10T07:30:00Z', + terminal_name: 'APM Los Angeles', + }, + }, + }, + { + type: 'tracking_request.created', + label: 'Tracking Started', + description: 'Triggered when a new tracking request is created and acknowledged', + category: 'Tracking', + payload: { + id: 'evt_abc131', + type: 'tracking_request.created', + created_at: '2025-02-01T12:00:00Z', + data: { + tracking_request_id: 'tr_jkl012', + request_number: 'MSCU1234567', + request_type: 'container', + shipping_line_scac: 'MSCU', + status: 'pending', + }, + }, + }, + { + type: 'tracking_request.failed', + label: 'Tracking Failed', + description: 'Triggered when a tracking request cannot be fulfilled', + category: 'Tracking', + payload: { + id: 'evt_abc132', + type: 'tracking_request.failed', + created_at: '2025-02-01T12:30:00Z', + data: { + tracking_request_id: 'tr_jkl013', + request_number: 'INVALID123', + request_type: 'container', + status: 'failed', + failed_reason: 'not_found', + error_message: 'Container number not found in carrier system', + }, + }, + }, +]; + +const webhookHandlerCode = `// Example: Express.js webhook handler +const express = require('express'); +const crypto = require('crypto'); + +const app = express(); +app.use(express.json()); + +// Verify webhook signature (recommended) +function verifySignature(payload, signature, secret) { + const expected = crypto + .createHmac('sha256', secret) + .update(JSON.stringify(payload)) + .digest('hex'); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expected) + ); +} + +// Webhook endpoint +app.post('/webhooks/terminal49', (req, res) => { + const signature = req.headers['x-t49-signature']; + const webhookSecret = process.env.WEBHOOK_SECRET; + + // Verify signature + if (!verifySignature(req.body, signature, webhookSecret)) { + return res.status(401).json({ error: 'Invalid signature' }); + } + + const event = req.body; + + // Handle different event types + switch (event.type) { + case 'container.availability.changed': + handleAvailabilityChange(event.data); + break; + case 'container.lfd.updated': + handleLfdUpdate(event.data); + break; + case 'container.hold.added': + handleHoldAdded(event.data); + break; + // ... handle other events + } + + // Always respond with 200 to acknowledge receipt + res.status(200).json({ received: true }); +}); + +// Event handlers +function handleAvailabilityChange(data) { + if (data.available_for_pickup) { + // Notify operations team + sendNotification(\`Container \${data.container_number} is ready for pickup!\`); + // Update your TMS + updateTMS(data.container_id, { status: 'available' }); + } +} + +function handleLfdUpdate(data) { + const daysUntilLfd = calculateDaysUntil(data.pickup_lfd); + if (daysUntilLfd <= 2) { + // Urgent notification + sendAlert(\`URGENT: LFD for \${data.container_number} is in \${daysUntilLfd} days!\`); + } +} + +function handleHoldAdded(data) { + // Log the hold for compliance + logHold(data); + // Notify compliance team + sendNotification(\`Hold placed on \${data.container_number}: \${data.hold_type}\`); +}`; + +const categories = ['All', 'Shipment', 'Container', 'Tracking']; + +export default function WebhooksPage() { + const [selectedCategory, setSelectedCategory] = useState('All'); + const [selectedEvent, setSelectedEvent] = useState(webhookEvents[0]); + + const filteredEvents = + selectedCategory === 'All' + ? webhookEvents + : webhookEvents.filter((e) => e.category === selectedCategory); + + return ( +
+ + +
+ {/* Info Banner */} +
+

Webhook Event Simulator

+

+ This page demonstrates webhook event structures. To configure real webhooks, visit the{' '} + + Terminal49 Dashboard + + . +

+
+ + {/* Category Filter */} +
+ {categories.map((category) => ( + + ))} +
+ + {/* Event Selector and Payload Display */} +
+ {/* Event List */} + + + Event Types ({filteredEvents.length}) + + +
+ {filteredEvents.map((event) => ( + + ))} +
+
+
+ + {/* Event Details */} + + + + {selectedEvent ? selectedEvent.label : 'Select an Event'} + + + + {selectedEvent ? ( +
+
+

Event Type

+ + {selectedEvent.type} + +
+ +
+

Description

+

{selectedEvent.description}

+
+ +
+

Example Payload

+
+                      {JSON.stringify(selectedEvent.payload, null, 2)}
+                    
+
+
+ ) : ( +

+ Select an event type to see its payload structure +

+ )} +
+
+
+ + {/* Webhook Handler Example */} + + + Webhook Handler Example + + +

+ Here's an example of how to handle Terminal49 webhooks in your application: +

+ +
+
+ + {/* Best Practices */} + + + Webhook Best Practices + + +
+
+

✅ Do

+
    +
  • + + Verify webhook signatures to ensure authenticity +
  • +
  • + + Respond with 200 status immediately, process async +
  • +
  • + + Implement idempotency to handle duplicate deliveries +
  • +
  • + + Log all webhook events for debugging +
  • +
  • + + Set up alerting for webhook failures +
  • +
+
+
+

❌ Don't

+
    +
  • + + Don't do heavy processing before responding +
  • +
  • + + Don't rely on webhooks as the only data source +
  • +
  • + + Don't ignore webhook signature verification +
  • +
  • + + Don't assume events arrive in order +
  • +
  • + + Don't expose your webhook endpoint publicly +
  • +
+
+
+
+
+ + {/* SDK Note */} +
+

SDK Note

+

+ The Terminal49 TypeScript SDK does not currently include methods for webhook management + (create, list, update, delete). Webhooks are configured through the Terminal49 web + dashboard. This is a known gap that may be addressed in future SDK versions. +

+
+
+
+ ); +} diff --git a/sdk-showcase/src/components/code-panel/CodePanel.tsx b/sdk-showcase/src/components/code-panel/CodePanel.tsx new file mode 100644 index 00000000..b092bb38 --- /dev/null +++ b/sdk-showcase/src/components/code-panel/CodePanel.tsx @@ -0,0 +1,100 @@ +'use client'; + +import { useState } from 'react'; +import { cn } from '@/lib/utils'; + +interface CodePanelProps { + title?: string; + code: string; + language?: string; + collapsible?: boolean; + defaultExpanded?: boolean; +} + +export function CodePanel({ + title = 'SDK Code', + code, + language = 'typescript', + collapsible = true, + defaultExpanded = false, +}: CodePanelProps) { + const [expanded, setExpanded] = useState(defaultExpanded); + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy:', err); + } + }; + + const toggleExpand = () => { + if (collapsible) { + setExpanded(!expanded); + } + }; + + return ( +
+ {/* Header */} +
+
{ + if (collapsible && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault(); + toggleExpand(); + } + }} + > + {collapsible && ( + + + + )} + {title} + + {language} + +
+ +
+ + {/* Code Block */} + {(!collapsible || expanded) && ( +
+
+            
+              {code.trim()}
+            
+          
+
+ )} +
+ ); +} diff --git a/sdk-showcase/src/components/code-panel/index.ts b/sdk-showcase/src/components/code-panel/index.ts new file mode 100644 index 00000000..824a9ad5 --- /dev/null +++ b/sdk-showcase/src/components/code-panel/index.ts @@ -0,0 +1 @@ +export { CodePanel } from './CodePanel'; diff --git a/sdk-showcase/src/components/features/Button.tsx b/sdk-showcase/src/components/features/Button.tsx new file mode 100644 index 00000000..8ad71ef2 --- /dev/null +++ b/sdk-showcase/src/components/features/Button.tsx @@ -0,0 +1,67 @@ +import { cn } from '@/lib/utils'; +import Link from 'next/link'; + +interface ButtonProps { + children: React.ReactNode; + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + size?: 'sm' | 'md' | 'lg'; + href?: string; + onClick?: () => void; + disabled?: boolean; + type?: 'button' | 'submit' | 'reset'; + className?: string; +} + +export function Button({ + children, + variant = 'primary', + size = 'md', + href, + onClick, + disabled, + type = 'button', + className, +}: ButtonProps) { + const baseStyles = + 'inline-flex items-center justify-center gap-1.5 font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-kumo-focus disabled:opacity-50 disabled:cursor-not-allowed'; + + const variantStyles = { + primary: 'bg-kumo-accent text-white hover:bg-kumo-accent-hover', + secondary: + 'bg-kumo-base border border-kumo-line text-kumo-secondary hover:text-kumo-default hover:bg-kumo-recessed', + ghost: 'text-kumo-secondary hover:bg-kumo-recessed hover:text-kumo-default', + danger: 'bg-red-600 text-white hover:bg-red-700', + }; + + const sizeStyles = { + sm: 'px-2.5 py-1 text-xs', + md: 'px-3 py-1.5 text-sm', + lg: 'px-4 py-2 text-sm', + }; + + const classes = cn( + baseStyles, + variantStyles[variant], + sizeStyles[size], + className + ); + + if (href) { + return ( + + {children} + + ); + } + + return ( + + ); +} diff --git a/sdk-showcase/src/components/features/Card.tsx b/sdk-showcase/src/components/features/Card.tsx new file mode 100644 index 00000000..755a6aed --- /dev/null +++ b/sdk-showcase/src/components/features/Card.tsx @@ -0,0 +1,59 @@ +import { cn } from '@/lib/utils'; + +interface CardProps { + children: React.ReactNode; + className?: string; +} + +export function Card({ children, className }: CardProps) { + return ( +
+ {children} +
+ ); +} + +interface CardHeaderProps { + children: React.ReactNode; + className?: string; +} + +export function CardHeader({ children, className }: CardHeaderProps) { + return ( +
+ {children} +
+ ); +} + +interface CardTitleProps { + children: React.ReactNode; + className?: string; +} + +export function CardTitle({ children, className }: CardTitleProps) { + return ( +

+ {children} +

+ ); +} + +interface CardContentProps { + children: React.ReactNode; + className?: string; +} + +export function CardContent({ children, className }: CardContentProps) { + return
{children}
; +} diff --git a/sdk-showcase/src/components/features/MetricCard.tsx b/sdk-showcase/src/components/features/MetricCard.tsx new file mode 100644 index 00000000..f95b2db5 --- /dev/null +++ b/sdk-showcase/src/components/features/MetricCard.tsx @@ -0,0 +1,69 @@ +import Link from 'next/link'; +import { cn } from '@/lib/utils'; + +interface MetricCardProps { + title: string; + value: number | string; + icon: string; + href?: string; + trend?: { + value: number; + direction: 'up' | 'down' | 'neutral'; + }; + variant?: 'default' | 'warning' | 'success'; +} + +export function MetricCard({ + title, + value, + icon, + href, + trend, + variant = 'default', +}: MetricCardProps) { + const variantStyles = { + default: 'border-kumo-line', + warning: 'border-amber-200 bg-amber-50 dark:bg-amber-950/20 dark:border-amber-800', + success: 'border-emerald-200 bg-emerald-50 dark:bg-emerald-950/20 dark:border-emerald-800', + }; + + const content = ( +
+
+
+

{title}

+

+ {value} +

+ {trend && ( +

+ {trend.direction === 'up' && '↑ '} + {trend.direction === 'down' && '↓ '} + {trend.value}% +

+ )} +
+ {icon} +
+
+ ); + + if (href) { + return {content}; + } + + return content; +} diff --git a/sdk-showcase/src/components/features/StatusBadge.tsx b/sdk-showcase/src/components/features/StatusBadge.tsx new file mode 100644 index 00000000..2192e5cf --- /dev/null +++ b/sdk-showcase/src/components/features/StatusBadge.tsx @@ -0,0 +1,30 @@ +import { cn, containerStatusConfig } from '@/lib/utils'; + +interface StatusBadgeProps { + status: string; + size?: 'sm' | 'md'; +} + +export function StatusBadge({ status, size = 'md' }: StatusBadgeProps) { + const config = containerStatusConfig[status] || { + label: status, + color: 'text-slate-600', + bgColor: 'bg-slate-100', + description: 'Unknown status', + }; + + return ( + + + {config.label} + + ); +} diff --git a/sdk-showcase/src/components/features/index.ts b/sdk-showcase/src/components/features/index.ts new file mode 100644 index 00000000..e585da8b --- /dev/null +++ b/sdk-showcase/src/components/features/index.ts @@ -0,0 +1,4 @@ +export { StatusBadge } from './StatusBadge'; +export { Card, CardHeader, CardTitle, CardContent } from './Card'; +export { MetricCard } from './MetricCard'; +export { Button } from './Button'; diff --git a/sdk-showcase/src/components/layout/PageHeader.tsx b/sdk-showcase/src/components/layout/PageHeader.tsx new file mode 100644 index 00000000..d657bd2c --- /dev/null +++ b/sdk-showcase/src/components/layout/PageHeader.tsx @@ -0,0 +1,58 @@ +import Link from 'next/link'; + +interface Breadcrumb { + label: string; + href?: string; +} + +interface PageHeaderProps { + title: string; + description?: string; + breadcrumbs?: Breadcrumb[]; + actions?: React.ReactNode; +} + +export function PageHeader({ + title, + description, + breadcrumbs, + actions, +}: PageHeaderProps) { + return ( +
+ {/* Breadcrumbs */} + {breadcrumbs && breadcrumbs.length > 0 && ( + + )} + + {/* Title and Actions */} +
+
+

{title}

+ {description && ( +

{description}

+ )} +
+ {actions &&
{actions}
} +
+
+ ); +} diff --git a/sdk-showcase/src/components/layout/Sidebar.tsx b/sdk-showcase/src/components/layout/Sidebar.tsx new file mode 100644 index 00000000..2cc0b329 --- /dev/null +++ b/sdk-showcase/src/components/layout/Sidebar.tsx @@ -0,0 +1,115 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { cn } from '@/lib/utils'; +import { ThemeToggle } from '@/components/theme-toggle'; + +interface NavItem { + label: string; + href: string; + children?: NavItem[]; +} + +const navigation: NavItem[] = [ + { label: 'Dashboard', href: '/' }, + { + label: 'Tracking', + href: '/tracking-requests', + children: [ + { label: 'Requests', href: '/tracking-requests' }, + { label: 'New Request', href: '/tracking-requests/new' }, + ], + }, + { + label: 'Shipments', + href: '/shipments', + children: [{ label: 'All Shipments', href: '/shipments' }], + }, + { + label: 'Containers', + href: '/containers', + children: [{ label: 'All Containers', href: '/containers' }], + }, + { + label: 'Reference', + href: '/shipping-lines', + children: [ + { label: 'Shipping Lines', href: '/shipping-lines' }, + { label: 'Webhooks', href: '/webhooks' }, + ], + }, + { label: 'Search', href: '/search' }, +]; + +export function Sidebar() { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/sdk-showcase/src/components/layout/index.ts b/sdk-showcase/src/components/layout/index.ts new file mode 100644 index 00000000..e7d18de9 --- /dev/null +++ b/sdk-showcase/src/components/layout/index.ts @@ -0,0 +1,2 @@ +export { Sidebar } from './Sidebar'; +export { PageHeader } from './PageHeader'; diff --git a/sdk-showcase/src/components/theme-toggle.tsx b/sdk-showcase/src/components/theme-toggle.tsx new file mode 100644 index 00000000..db2b5f44 --- /dev/null +++ b/sdk-showcase/src/components/theme-toggle.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +export function ThemeToggle() { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + // Check for saved preference or system preference + const saved = localStorage.getItem('theme'); + if (saved === 'dark') { + setTheme('dark'); + document.documentElement.setAttribute('data-theme', 'dark'); + } + }, []); + + const toggleTheme = () => { + const newTheme = theme === 'light' ? 'dark' : 'light'; + setTheme(newTheme); + + if (newTheme === 'dark') { + document.documentElement.setAttribute('data-theme', 'dark'); + localStorage.setItem('theme', 'dark'); + } else { + document.documentElement.removeAttribute('data-theme'); + localStorage.setItem('theme', 'light'); + } + }; + + return ( + + ); +} diff --git a/sdk-showcase/src/lib/terminal49/client.ts b/sdk-showcase/src/lib/terminal49/client.ts new file mode 100644 index 00000000..d9552b8e --- /dev/null +++ b/sdk-showcase/src/lib/terminal49/client.ts @@ -0,0 +1,55 @@ +import { Terminal49Client } from '@terminal49/sdk'; + +/** + * Creates a Terminal49 SDK client singleton for server-side use. + * + * SDK Method Coverage: + * - This client provides access to all SDK methods + * - Resource namespaces: shipments, containers, trackingRequests, shippingLines + * - Direct methods: search, getDemurrage, getRailMilestones, trackContainer + */ + +let clientInstance: Terminal49Client | null = null; + +/** + * Check if the API token is configured + */ +export function hasApiToken(): boolean { + return !!process.env.T49_API_TOKEN; +} + +export function getClient(): Terminal49Client { + if (!clientInstance) { + const apiToken = process.env.T49_API_TOKEN; + + if (!apiToken) { + throw new Error( + 'T49_API_TOKEN environment variable is required. ' + + 'Get your API key from https://app.terminal49.com/settings/api' + ); + } + + clientInstance = new Terminal49Client({ + apiToken, + defaultFormat: 'mapped', // Use mapped format for cleaner responses + }); + } + + return clientInstance; +} + +/** + * Get a fresh client instance (useful for testing or when token changes) + */ +export function createClient(apiToken?: string): Terminal49Client { + const token = apiToken || process.env.T49_API_TOKEN; + + if (!token) { + throw new Error('API token is required'); + } + + return new Terminal49Client({ + apiToken: token, + defaultFormat: 'mapped', + }); +} diff --git a/sdk-showcase/src/lib/terminal49/index.ts b/sdk-showcase/src/lib/terminal49/index.ts new file mode 100644 index 00000000..a6f33348 --- /dev/null +++ b/sdk-showcase/src/lib/terminal49/index.ts @@ -0,0 +1 @@ +export { getClient, createClient, hasApiToken } from './client'; diff --git a/sdk-showcase/src/lib/utils.ts b/sdk-showcase/src/lib/utils.ts new file mode 100644 index 00000000..483eeb4a --- /dev/null +++ b/sdk-showcase/src/lib/utils.ts @@ -0,0 +1,133 @@ +import { clsx, type ClassValue } from 'clsx'; + +/** + * Utility function to merge Tailwind CSS classes + */ +export function cn(...inputs: ClassValue[]) { + return clsx(inputs); +} + +/** + * Container status display configuration + */ +export const containerStatusConfig: Record< + string, + { label: string; color: string; bgColor: string; description: string } +> = { + new: { + label: 'New', + color: 'text-gray-600', + bgColor: 'bg-gray-100', + description: 'Tracking started, status unknown', + }, + on_ship: { + label: 'On Ship', + color: 'text-blue-600', + bgColor: 'bg-blue-100', + description: 'In transit by vessel', + }, + available: { + label: 'Available', + color: 'text-green-600', + bgColor: 'bg-green-100', + description: 'Ready for pickup', + }, + not_available: { + label: 'Not Available', + color: 'text-yellow-600', + bgColor: 'bg-yellow-100', + description: 'At port but restricted', + }, + grounded: { + label: 'Grounded', + color: 'text-orange-600', + bgColor: 'bg-orange-100', + description: 'Availability unknown', + }, + awaiting_inland_transfer: { + label: 'Awaiting Inland', + color: 'text-purple-600', + bgColor: 'bg-purple-100', + description: 'Waiting for rail transfer', + }, + on_rail: { + label: 'On Rail', + color: 'text-indigo-600', + bgColor: 'bg-indigo-100', + description: 'In transit by rail', + }, + picked_up: { + label: 'Picked Up', + color: 'text-teal-600', + bgColor: 'bg-teal-100', + description: 'Out for delivery', + }, + off_dock: { + label: 'Off Dock', + color: 'text-cyan-600', + bgColor: 'bg-cyan-100', + description: 'At alternative facility', + }, + delivered: { + label: 'Delivered', + color: 'text-emerald-600', + bgColor: 'bg-emerald-100', + description: 'Delivery confirmed', + }, + empty_returned: { + label: 'Empty Returned', + color: 'text-gray-500', + bgColor: 'bg-gray-100', + description: 'Container returned', + }, +}; + +/** + * Format a date string for display + */ +export function formatDate(dateString: string | null | undefined): string { + if (!dateString) return '—'; + try { + return new Date(dateString).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); + } catch { + return dateString; + } +} + +/** + * Format a datetime string for display + */ +export function formatDateTime(dateString: string | null | undefined): string { + if (!dateString) return '—'; + try { + return new Date(dateString).toLocaleString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + timeZoneName: 'short', + }); + } catch { + return dateString; + } +} + +/** + * Calculate days remaining until a date + */ +export function daysUntil(dateString: string | null | undefined): number | null { + if (!dateString) return null; + try { + const targetDate = new Date(dateString); + const now = new Date(); + const diffTime = targetDate.getTime() - now.getTime(); + return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + } catch { + return null; + } +} diff --git a/sdk-showcase/src/types/index.ts b/sdk-showcase/src/types/index.ts new file mode 100644 index 00000000..c73bf9c1 --- /dev/null +++ b/sdk-showcase/src/types/index.ts @@ -0,0 +1,78 @@ +/** + * Container status values from Terminal49 API + */ +export type ContainerStatus = + | 'new' + | 'on_ship' + | 'available' + | 'not_available' + | 'grounded' + | 'awaiting_inland_transfer' + | 'on_rail' + | 'picked_up' + | 'off_dock' + | 'delivered' + | 'empty_returned' + | 'dropped' + | 'loaded'; + +/** + * Status badge configuration + */ +export interface StatusConfig { + label: string; + color: string; + bgColor: string; + description: string; +} + +/** + * SDK method showcase metadata + */ +export interface MethodShowcase { + name: string; + namespace: string; + description: string; + codeExample: string; + page: string; +} + +/** + * Dashboard summary stats + */ +export interface DashboardStats { + totalShipments: number; + totalContainers: number; + inTransit: number; + available: number; + urgentLfd: number; + pendingRequests: number; +} + +/** + * LFD Alert item + */ +export interface LfdAlert { + containerId: string; + containerNumber: string; + lfd: string; + daysRemaining: number; + terminal: string; +} + +/** + * Tracking request inference result + */ +export interface InferenceResult { + number_type: 'container' | 'booking' | 'bill_of_lading'; + decision: 'auto_select' | 'needs_confirmation'; + candidates: Array<{ + scac: string; + name: string; + confidence: number; + }>; + selected?: { + scac: string; + name: string; + }; +} diff --git a/sdk-showcase/tailwind.config.ts b/sdk-showcase/tailwind.config.ts new file mode 100644 index 00000000..bf8c4038 --- /dev/null +++ b/sdk-showcase/tailwind.config.ts @@ -0,0 +1,50 @@ +import type { Config } from 'tailwindcss'; + +const config: Config = { + content: [ + './src/pages/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}', + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + colors: { + // Kumo semantic color tokens + kumo: { + // Surfaces + base: 'var(--kumo-bg-base)', + elevated: 'var(--kumo-bg-elevated)', + recessed: 'var(--kumo-bg-recessed)', + // Text + default: 'var(--kumo-text-default)', + secondary: 'var(--kumo-text-secondary)', + muted: 'var(--kumo-text-muted)', + link: 'var(--kumo-text-link)', + // Borders + line: 'var(--kumo-border-line)', + focus: 'var(--kumo-border-focus)', + // Accent + accent: 'var(--kumo-accent)', + 'accent-hover': 'var(--kumo-accent-hover)', + }, + // Container status colors + status: { + new: '#6b7280', // gray + 'on-ship': '#3b82f6', // blue + available: '#22c55e', // green + 'not-available': '#eab308', // yellow + grounded: '#f97316', // orange + 'awaiting-inland': '#a855f7', // purple + 'on-rail': '#6366f1', // indigo + 'picked-up': '#14b8a6', // teal + 'off-dock': '#06b6d4', // cyan + delivered: '#10b981', // emerald + 'empty-returned': '#9ca3af', // gray + }, + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/sdk-showcase/tsconfig.json b/sdk-showcase/tsconfig.json new file mode 100644 index 00000000..a992e12c --- /dev/null +++ b/sdk-showcase/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/sdk-showcase/vercel.json b/sdk-showcase/vercel.json new file mode 100644 index 00000000..624e6f41 --- /dev/null +++ b/sdk-showcase/vercel.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "nextjs", + "buildCommand": "cd ../sdks/typescript-sdk && npm run build && cd ../../sdk-showcase && pnpm build", + "installCommand": "cd ../sdks/typescript-sdk && npm install && cd ../../sdk-showcase && pnpm install" +}