13 Commits

Author SHA1 Message Date
Claude Code Bot 59bfdbc3ab fix: resolve issue #17 - Product page: Implement Breadcrumb 2026-03-30 11:13:31 +00:00
yen.nguyen ec13d4afcc Update .gitea/workflows/claude.yml 2026-03-30 10:51:16 +00:00
yen.nguyen 59b5c3245c fix claude prompt 2026-03-30 12:28:56 +02:00
yen.nguyen 63d41399b8 add more claude-interaction 2026-03-30 12:21:18 +02:00
yen.nguyen 9f5e45aaae add PRD to docs 2026-03-27 23:44:24 +01:00
yen.nguyen e378b4d3e1 Update .gitea/workflows/claude.yml 2026-03-27 17:47:44 +00:00
yen.nguyen 9d8cfabb21 Update .gitea/workflows/claude.yml 2026-03-27 17:46:38 +00:00
yen.nguyen 6ee119e398 Add .gitea/workflows/claude.yml 2026-03-27 15:56:08 +00:00
Nam Doan b55e3d7a1a bugfix: restyle vt-subcription 3bear json config file 2026-01-09 14:14:41 +07:00
Nam Doan 9622af2c9e bugfix: add optional check undefind subtext susbcription 2026-01-09 13:54:08 +07:00
namds29 973b1bdb68 Merge pull request 'update: add vt-subcription component into design vibentec json file' (#37) from namds/implement-highlight-and-subcription-section into main
Reviewed-on: #37
2026-01-09 03:01:59 +00:00
namds29 cf4aedab84 Merge pull request 'namds/implement-highlight-and-subcription-section' (#35) from namds/implement-highlight-and-subcription-section into main
Reviewed-on: #35
2026-01-07 03:16:53 +00:00
namds29 d645c01323 Merge pull request 'feat: create highlight category component and config with json file' (#34) from namds/implement-highlight-category into main
Reviewed-on: #34
2026-01-06 16:13:39 +00:00
9 changed files with 737 additions and 283 deletions
+244
View File
@@ -0,0 +1,244 @@
name: Claude Code Issue Handler
on:
issue_comment:
types: [created]
env:
CLAUDE_MAX_TURNS: "30"
jobs:
handle-issue:
# Only run on issue comments, not PR comments
if: ${{ !github.event.issue.pull_request }}
runs-on: ubuntu-latest
steps:
- name: Classify issue by labels
id: classify
run: |
LABELS='${{ toJson(github.event.issue.labels) }}'
echo "Raw labels: $LABELS"
IS_BUG=$(echo "$LABELS" | jq '[.[].name | ascii_downcase] | any(. == "bug")' 2>/dev/null || echo "false")
IS_ENHANCEMENT=$(echo "$LABELS" | jq '[.[].name | ascii_downcase] | any(. == "enhancement")' 2>/dev/null || echo "false")
echo "is_bug=$IS_BUG" >> $GITHUB_OUTPUT
echo "is_enhancement=$IS_ENHANCEMENT" >> $GITHUB_OUTPUT
echo "is_bug=$IS_BUG | is_enhancement=$IS_ENHANCEMENT"
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITEA_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code
- name: Authenticate Claude Code CLI
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude config set apiKey "$ANTHROPIC_API_KEY"
claude config set autoUpdaterStatus disabled
- name: Configure Git
run: |
git config --global user.email "claude@vibentec-it.io"
git config --global user.name "Claude Code Bot"
git config --global url."https://oauth2:${{ secrets.GITEA_TOKEN }}@gitea.vibentec-it.io/".insteadOf "https://gitea.vibentec-it.io/"
# ─────────────────────────────────────────────
# BUG FLOW: research + fix + PR
# ─────────────────────────────────────────────
- name: "[Bug] Checkout fix branch"
if: steps.classify.outputs.is_bug == 'true'
run: |
BRANCH_NAME="fix/issue-${{ github.event.issue.number }}"
git checkout -b "$BRANCH_NAME"
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
- name: "[Bug] Run Claude Code to research and fix"
if: steps.classify.outputs.is_bug == 'true'
id: claude-bug
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_COMMENT: ${{ github.event.comment.body }}
run: |
PROMPT=$(printf '%s\n' \
"You are a software engineer fixing a bug reported in issue #${ISSUE_NUMBER}: \"${ISSUE_TITLE}\"." \
"Issue description: ${ISSUE_BODY}" \
"Comment that triggered this action: ${ISSUE_COMMENT}" \
"Your tasks:" \
"1. Read and understand the codebase relevant to this bug." \
"2. Identify the root cause and which files need to change." \
"3. Implement minimal, focused fixes — do not refactor unrelated code." \
"4. After making code changes, run \"cd homepage && npm run lint\" via the Bash tool" \
" and immediately fix every lint error or syntax warning it reports." \
"5. Do NOT run Jenkinsfile or any Jenkins-related checks." \
"6. Do NOT run git commands yourself; the CI pipeline will commit your changes." \
"After completing your investigation, fixes, and lint pass, output a brief plain-text" \
"summary of the root cause, what you changed, and the lint result. Keep it under 200 words." \
)
claude \
--allowedTools "Read,Write,Edit,Bash,Glob,Grep,WebSearch,WebFetch" \
--max-turns "$CLAUDE_MAX_TURNS" \
--output-format text \
-p "$PROMPT" > /tmp/claude_bug_output.txt 2>&1 || true
echo "--- Claude output ---"
cat /tmp/claude_bug_output.txt
- name: "[Bug] Check for file changes"
if: steps.classify.outputs.is_bug == 'true'
id: git-status
run: |
if git diff --quiet && git diff --cached --quiet; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No file changes detected."
else
echo "has_changes=true" >> $GITHUB_OUTPUT
git diff --stat
fi
- name: "[Bug] Commit changes"
if: steps.classify.outputs.is_bug == 'true' && steps.git-status.outputs.has_changes == 'true'
run: |
git add -A
git commit -m "fix: resolve issue #${{ github.event.issue.number }} - ${{ github.event.issue.title }}"
- name: "[Bug] Push branch"
if: steps.classify.outputs.is_bug == 'true' && steps.git-status.outputs.has_changes == 'true'
run: git push origin "$BRANCH_NAME"
- name: "[Bug] Create Pull Request"
if: steps.classify.outputs.is_bug == 'true' && steps.git-status.outputs.has_changes == 'true'
id: create-pr
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
BASE_URL: ${{ github.server_url }}
run: |
CLAUDE_SUMMARY=$(cat /tmp/claude_bug_output.txt | tail -n 50)
PR_TITLE="fix: resolve issue #${ISSUE_NUMBER} - ${ISSUE_TITLE}"
PR_BODY=$(printf '## Summary\n\nThis PR addresses the bug reported in issue #%s.\n\n### Claude Code Analysis\n%s\n\n---\nCloses #%s' \
"$ISSUE_NUMBER" "$CLAUDE_SUMMARY" "$ISSUE_NUMBER")
PAYLOAD=$(jq -n \
--arg title "$PR_TITLE" \
--arg body "$PR_BODY" \
--arg head "$BRANCH_NAME" \
--arg base "main" \
'{title: $title, body: $body, head: $head, base: $base}')
PR_RESPONSE=$(curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"${BASE_URL}/api/v1/repos/${REPO}/pulls" \
-d "$PAYLOAD")
PR_NUMBER=$(echo "$PR_RESPONSE" | jq -r '.number // empty')
PR_URL=$(echo "$PR_RESPONSE" | jq -r '.html_url // empty')
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
echo "Created PR #$PR_NUMBER: $PR_URL"
- name: "[Bug] Comment on issue — PR created"
if: >-
steps.classify.outputs.is_bug == 'true' &&
steps.git-status.outputs.has_changes == 'true' &&
steps.create-pr.outputs.pr_url != ''
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
BASE_URL: ${{ github.server_url }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
PR_URL: ${{ steps.create-pr.outputs.pr_url }}
run: |
COMMENT=$(printf "I've analyzed this bug and implemented a fix.\n\n**Pull Request:** [PR #%s](%s)\n\nPlease review the changes and merge when ready. If the fix doesn't fully address the issue, feel free to leave a comment and I'll take another look." \
"$PR_NUMBER" "$PR_URL")
PAYLOAD=$(jq -n --arg body "$COMMENT" '{body: $body}')
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"${BASE_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \
-d "$PAYLOAD"
- name: "[Bug] Comment on issue — no code changes needed"
if: steps.classify.outputs.is_bug == 'true' && steps.git-status.outputs.has_changes == 'false'
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
BASE_URL: ${{ github.server_url }}
run: |
ANALYSIS=$(cat /tmp/claude_bug_output.txt 2>/dev/null || echo "No analysis output available.")
COMMENT=$(printf "After researching this issue, **no code changes appear to be necessary** at this time.\n\n### Analysis\n%s\n\nIf you believe something was missed, please provide more details and I'll investigate further." \
"$ANALYSIS")
PAYLOAD=$(jq -n --arg body "$COMMENT" '{body: $body}')
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"${BASE_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \
-d "$PAYLOAD"
# ─────────────────────────────────────────────
# ENHANCEMENT FLOW: research + comment only
# ─────────────────────────────────────────────
- name: "[Enhancement] Run Claude Code to research"
if: steps.classify.outputs.is_enhancement == 'true'
id: claude-enhancement
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_COMMENT: ${{ github.event.comment.body }}
run: |
PROMPT=$(printf '%s\n' \
"You are a software architect reviewing a feature idea from issue #${ISSUE_NUMBER}: \"${ISSUE_TITLE}\"." \
"Idea description: ${ISSUE_BODY}" \
"Comment that triggered this action: ${ISSUE_COMMENT}" \
"Your tasks:" \
"1. Explore the existing codebase to understand what already exists related to this idea." \
"2. Research best practices and possible implementation approaches." \
"3. Identify technical challenges, risks, or dependencies." \
"4. Provide a structured Markdown response suitable for posting as an issue comment." \
"Format your response with these sections:" \
"- **Current State** (what already exists)" \
"- **Proposed Approach** (recommended implementation path)" \
"- **Considerations** (risks, trade-offs, dependencies)" \
"- **Next Steps** (actionable recommendations)" \
"Keep the total response under 500 words." \
)
claude \
--allowedTools "Read,Glob,Grep,WebSearch,WebFetch" \
--max-turns 15 \
--output-format text \
-p "$PROMPT" > /tmp/claude_enhancement_output.txt 2>&1 || true
echo "--- Claude output ---"
cat /tmp/claude_enhancement_output.txt
- name: "[Enhancement] Comment research findings on issue"
if: steps.classify.outputs.is_enhancement == 'true'
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
BASE_URL: ${{ github.server_url }}
run: |
RESEARCH=$(cat /tmp/claude_enhancement_output.txt 2>/dev/null || echo "Research output unavailable.")
COMMENT=$(printf "## Research Findings\n\nI've explored the codebase and researched this idea. Here's what I found:\n\n%s\n\n---\n*This research was performed automatically by Claude Code. Feel free to discuss further in the comments.*" \
"$RESEARCH")
PAYLOAD=$(jq -n --arg body "$COMMENT" '{body: $body}')
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"${BASE_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}/comments" \
-d "$PAYLOAD"
+6 -1
View File
@@ -563,15 +563,20 @@
"cardClassName": "rounded-2xl overflow-hidden bg-[#CFECD9] w-[800px] p-10",
"title": "10% für dich!",
"titleClassName": "text-[#003F31] text-[28px] font-bold text-center",
"highlightClassName": "text-[#003F31] font-bold",
"description": true,
"formClassName": "mt-8 flex flex-col items-center gap-4",
"descriptionClassName": "text-[#003F31] text-[16px] text-center",
"fieldsClassName": "grid grid-cols-1 small:grid-cols-2 gap-4 w-full",
"descriptionPrefix": "Melde dich jetzt zum 3Bears Newsletter an und sichere dir",
"descriptionHighlight": "10% Rabatt auf deinen nächsten Einkauf!",
"subtextClassName": "text-[#003F31] text-[16px] text-center",
"descriptionSuffix": "",
"subtext": "Deinen Rabattcode bekommst du von uns per Mail.",
"firstName": { "placeholder": "Vorname" },
"email": { "placeholder": "E-Mail-Adresse" },
"policyLabel": "Ich habe die DSGVO gelesen und akzeptiere sie.",
"cta": { "label": "Anmelden" }
"cta": { "label": "Anmelden", "className": "bg-[#FCEE56] text-[#0D382E] px-6 py-2 rounded-full w-fit font-bold" }
}
}
},
+340
View File
@@ -0,0 +1,340 @@
# Shop-Storefront — Current State Document
> **Generated:** 2026-03-27
> **Role:** Brownfield analysis by Senior Staff Engineer / Technical Lead
> **Branch at time of analysis:** `namds/refactor-base-layout`
---
## Table of Contents
1. [Phase 1 — Discovery & Scaffolding](#phase-1--discovery--scaffolding)
2. [Phase 2 — Codebase Triage](#phase-2--codebase-triage)
3. [Phase 3 — Documentation](#phase-3--documentation)
- [High-Level System Overview](#1-high-level-system-overview)
- [Architecture Diagram](#2-architecture-diagram)
- [Local Setup Guide](#3-local-setup-guide)
- [Key Domain Entities](#4-key-domain-entities)
4. [Phase 4 — Maintenance Strategy](#phase-4--maintenance-strategy)
- [Immediate Risks](#1-immediate-risks)
- [Refactoring Opportunities](#2-refactoring-opportunities-low-hanging-fruit)
- [Testing Strategy](#3-testing-strategy)
---
## Phase 1 — Discovery & Scaffolding
### Tech Stack
| Layer | Technology |
| ----------------------- | --------------------------------------------------------- |
| **Framework** | Next.js 15 (App Router, Turbopack) |
| **Language** | TypeScript 5.3 |
| **UI Runtime** | React 19 RC (`19.0.0-rc-66855b96-20241106`) |
| **Styling** | Tailwind CSS 3 + `@medusajs/ui` + Radix UI |
| **Commerce Backend** | Medusa V2 (`@medusajs/js-sdk`) |
| **Payments** | Stripe (`@stripe/react-stripe-js`), PayPal |
| **Package Manager** | Yarn 3 (Berry) |
| **Database (indirect)** | `pg` listed as dependency — Medusa owns the DB connection |
### Entry Points & Critical Files
| File | Role |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `src/middleware.ts` | **True entry point.** Intercepts every request, resolves country code from Vercel geo-header or URL, and redirects to `/{countryCode}/...` |
| `src/app/layout.tsx` | Root Next.js layout (HTML shell, global styles) |
| `src/app/[countryCode]/(main)/layout.tsx` | **Primary layout shell.** Loads the JSON design config, fetches cart + customer, and hands everything to `DynamicLayoutRenderer` |
| `src/vibentec/configloader.ts` | Reads active design JSON from `config/` at request time |
| `src/vibentec/devJsonFileNames.ts` | Hardcoded switch that selects which tenant/design JSON is active |
| `src/lib/config.ts` | Instantiates the Medusa JS SDK singleton |
| `src/lib/data/` | All server-side data fetching (cart, customer, products, orders, regions, etc.) |
---
## Phase 2 — Codebase Triage
### Architectural Pattern
This is a **modular monolith storefront** using Next.js App Router with a custom **JSON-driven dynamic layout layer** on top. There are two distinct halves:
1. **Standard Medusa Storefront** — The base is a Medusa V2 Next.js starter. It follows a clear `modules/``templates/``components/` hierarchy, with server-side data fetching in `lib/data/` feeding React Server Components.
2. **Vibentec UI Builder** — A bespoke system in `src/vibentec/` that allows the entire page shell (header, nav, footer, banners) to be defined declaratively in JSON config files (`/config/`). At runtime, `DynamicLayoutRenderer` walks the JSON node tree and maps keys to React components via `componentMap`. Multiple tenant/brand configs exist: `3bear`, `drsquatch`, `vibentec`, `mds-starter`, `medusa-starter`, `playground`.
### Core Domain
`src/lib/data/` is the data layer. `src/modules/` holds all business-domain UI (account, cart, checkout, products, orders, collections, store). The custom differentiator is `src/vibentec/` — this is Vibentec's intellectual property layered on top of the OSS Medusa starter.
### Technical Debt & Health Assessment
| Severity | Issue |
| ---------- | ----------------------------------------------------------------------------------------------------------------- |
| **High** | `REVALIDATE_SECRET=supersecret` is committed in `.env` — must be rotated before production |
| **High** | A real publishable API key (`pk_65b8a...`) is hardcoded in the committed `.env` file |
| **Medium** | React 19 **RC** (release candidate) is pinned — not a stable release |
| **Medium** | Active tenant config is hardcoded in `src/vibentec/devJsonFileNames.ts` — switching brands requires a code change |
| **Medium** | `LayoutContext` uses `any` for `customer` and `cart` — kills type safety at the renderer boundary |
| **Medium** | `pg` is listed as a direct dependency but no direct DB usage exists in this storefront |
| **Low** | Zero test files found anywhere in the project |
| **Low** | `@types/react-instantsearch-dom` in devDeps but no instantsearch code exists — unused dependency |
---
## Phase 3 — Documentation
### 1. High-Level System Overview
This is a **multi-tenant e-commerce storefront** built on Next.js 15 (App Router), serving as the customer-facing frontend for a Medusa V2 headless commerce backend. The application handles the full retail customer journey: browsing products by category or collection, managing a cart, checking out with Stripe or PayPal, and managing account details and order history. All routes are prefixed with a country code (e.g., `/us/`, `/de/`), which is resolved at the edge by middleware that consults Medusa's region API and Vercel's geo-IP headers to deliver region-appropriate pricing and shipping rules.
The most significant custom layer is the **Vibentec UI Builder** (`src/vibentec/`). Rather than hard-coding the page shell (header, nav, banner bar, footer), the entire layout is declared as a JSON component tree stored in `config/*.design.json` files. At request time, the primary layout server component reads the active JSON file, passes it to `DynamicLayoutRenderer`, which walks the tree and resolves component names to actual React components via `componentMap`. This makes the full layout of the storefront configurable without touching React code — a white-label capability designed to serve multiple brands from a single codebase.
Data flows unidirectionally from the Medusa backend through server-side `"use server"` functions in `src/lib/data/`, which use the Medusa JS SDK with Next.js `force-cache` and tag-based revalidation. There is no client-side global state management (no Redux, Zustand, or React Query). The only React Context used is a minimal `ModalContext` scoped to the modal component. All authentication state is conveyed via cookies and forwarded as HTTP headers to Medusa.
---
### 2. Architecture Diagram
```mermaid
graph TD
subgraph "Browser"
Browser["Customer Browser"]
end
subgraph "Next.js 15 App (Port 8000)"
Middleware["src/middleware.ts\n(Edge Runtime)\nGeo-IP → Country Code\nRegion redirect"]
subgraph "App Router"
RootLayout["app/layout.tsx\nRoot HTML shell"]
MainLayout["app/[countryCode]/(main)/layout.tsx\nLoads design JSON\nFetches cart + customer\nDynamicLayoutRenderer"]
CheckoutLayout["app/[countryCode]/(checkout)/layout.tsx\nCheckout shell"]
subgraph "Pages"
Home["/ (main) page.tsx"]
Store["/store page.tsx"]
Product["/products/[handle]"]
Cart["/cart"]
Checkout["/checkout"]
Account["/account (Parallel Routes)"]
Order["/order/[id]"]
Collections["/collections/[handle]"]
Categories["/categories/[...category]"]
end
end
subgraph "Vibentec UI Builder (src/vibentec/)"
ConfigLoader["configloader.ts\nReads config/*.design.json"]
Renderer["renderer.tsx\nDynamicLayoutRenderer"]
ComponentMap["component-map.tsx\ncomponentMap registry"]
end
subgraph "Modules (src/modules/)"
Layout["layout/\nHeader, Nav, Footer,\nCart Button, Side Menu,\nMega Menu, Country Select"]
Products["products/\nGallery, Actions, Price,\nPreview, Tabs, Related"]
CartMod["cart/\nItems, Summary, Preview"]
CheckoutMod["checkout/\nAddresses, Payment,\nShipping, Review"]
AccountMod["account/\nProfile, Orders,\nAddresses, Login"]
OrderMod["order/\nConfirmation, Details,\nTransfer"]
end
subgraph "Data Layer (src/lib/data/)"
DataFunctions["cart.ts · products.ts\ncustomer.ts · orders.ts\ncategories.ts · collections.ts\nregions.ts · payment.ts\nfulfillment.ts · cookies.ts"]
end
subgraph "Config Files (config/)"
DesignJSON["nam.vibentec.design.json\nnam.3bear.design.json\nnam.drsquatch.design.json\nste.medusa-starter.design.json\n(+ others)"]
end
end
subgraph "External Services"
MedusaBackend["Medusa V2 Backend\n(localhost:9000)\nProducts, Orders, Carts,\nCustomers, Regions,\nPayment Sessions"]
Stripe["Stripe\nPayment Processing"]
PayPal["PayPal\nPayment Processing"]
S3["AWS S3\nProduct Images"]
end
Browser -->|"HTTP Request"| Middleware
Middleware -->|"Resolves region\nSets _medusa_cache_id cookie"| RootLayout
RootLayout --> MainLayout
MainLayout --> ConfigLoader
ConfigLoader -->|"Reads active .design.json"| DesignJSON
MainLayout --> Renderer
Renderer --> ComponentMap
ComponentMap --> Layout
MainLayout -->|"props.children"| Pages
Pages --> Products
Pages --> CartMod
Pages --> CheckoutMod
Pages --> AccountMod
Pages --> OrderMod
DataFunctions -->|"Medusa JS SDK\nforce-cache + tag revalidation"| MedusaBackend
CheckoutMod -->|"Stripe Elements"| Stripe
CheckoutMod -->|"PayPal SDK"| PayPal
MedusaBackend -->|"Image URLs"| S3
Products & CartMod & CheckoutMod & AccountMod & OrderMod --> DataFunctions
```
---
### 3. Local Setup Guide
**Prerequisites:** Node.js ≥ 20, Yarn 3 (Berry), a running Medusa V2 backend with at least one Region configured.
```bash
# 1. Clone and install
git clone <repo-url>
cd Shop-Storefront
yarn install
# 2. Configure environment
cp .env .env.local
# Edit .env.local and set:
# MEDUSA_BACKEND_URL=http://localhost:9000
# NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=<your key from Medusa Admin>
# NEXT_PUBLIC_BASE_URL=http://localhost:8000
# NEXT_PUBLIC_DEFAULT_REGION=us
# REVALIDATE_SECRET=<generate a strong random string — never use "supersecret">
# 3. Select the active brand design (defaults to namVibentec)
# Edit src/vibentec/devJsonFileNames.ts:
# const fileName = jsonFileNames.namVibentec; ← change to desired brand key
# 4. Run development server (Turbopack, port 8000)
yarn dev
# 5. Open browser — middleware will redirect to /{DEFAULT_REGION}/
open http://localhost:8000
```
**Build for production:**
```bash
yarn build
yarn start
```
> `next.config.js` calls `checkEnvVariables()` at startup. Missing required env vars will cause a descriptive build failure.
---
### 4. Key Domain Entities
Inferred from `@medusajs/types` (`HttpTypes`) as used by the data layer, plus local types in `src/types/global.ts`.
| Entity | Key Fields | Source |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **StoreProduct** | `id`, `title`, `handle`, `thumbnail`, `variants[]`, `tags[]`, `metadata`, `collection`, `categories[]` | `lib/data/products.ts` |
| **StoreProductVariant** | `id`, `calculated_price`, `inventory_quantity`, `options[]` | Fetched with `*variants.calculated_price` field selector |
| **StoreCart** | `id`, `region_id`, `items[]`, `shipping_address`, `billing_address`, `email`, `promotions[]`, `shipping_methods[]`, `payment_collection` | `lib/data/cart.ts` |
| **StoreLineItem** | `id`, `variant_id`, `quantity`, `total`, `product`, `variant`, `thumbnail`, `metadata` | Part of StoreCart |
| **StoreRegion** | `id`, `countries[]` (each with `iso_2`) | `middleware.ts`, `lib/data/regions.ts` |
| **StoreCustomer** | `id`, `email`, `first_name`, `last_name`, `addresses[]`, `phone` | `lib/data/customer.ts` |
| **StoreOrder** | `id`, `status`, `items[]`, `shipping_address`, `payment_collections[]`, `fulfillments[]` | `lib/data/orders.ts` |
| **StoreCartShippingOption** | `id`, `name`, `amount`, `provider_id` | `lib/data/cart.ts``listCartOptions()` |
| **FeaturedProduct** _(local)_ | `id`, `title`, `handle`, `thumbnail` | `src/types/global.ts` |
| **VariantPrice** _(local)_ | `calculated_price`, `original_price`, `currency_code`, `price_type`, `percentage_diff` | `src/types/global.ts` |
| **StoreFreeShippingPrice** _(local)_ | extends `StorePrice` + `target_reached`, `target_remaining`, `remaining_percentage` | `src/types/global.ts` |
| **LayoutComponentNode** _(local)_ | `{ [ComponentName]: { config?, children? } }` | `src/vibentec/component-map.tsx` |
---
## Phase 4 — Maintenance Strategy
### 1. Immediate Risks
| Severity | Risk | Location | Action Required |
| ------------ | ---------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Critical** | Secrets committed to repository | `.env` | Rotate `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` immediately. Regenerate `REVALIDATE_SECRET`. Use `.env.local` (already `.gitignore`'d) for real values. |
| **Critical** | Silent bug: missing `await` | `src/lib/data/cart.ts:339` | `const cartId = getCartId()` is missing `await`. `cartId` resolves to a `Promise` (truthy), so the guard `if (!cartId)` never fires. `setAddresses` silently fails to validate cart existence on cold sessions. Fix: `const cartId = await getCartId()` |
| **High** | Empty stub functions silently do nothing | `src/lib/data/cart.ts:279318` | `applyGiftCard`, `removeDiscount`, `removeGiftCard` are exported but completely empty. Any UI that calls them will silently succeed with no effect. Either implement for Medusa V2 or remove the exports. |
| **High** | TypeScript & ESLint errors suppressed at build | `next.config.js:2427` | `ignoreBuildErrors: true` and `ignoreDuringBuilds: true` ship broken types silently. Re-enable both before production and resolve resulting errors. |
| **Medium** | React 19 RC pinned | `package.json` | Pre-release runtime in production. Migrate to stable React 19 once available, or document the risk explicitly. |
---
### 2. Refactoring Opportunities (Low-Hanging Fruit)
Listed in priority order — highest impact for lowest effort first.
**1. Externalize active brand config to an environment variable**
- File: `src/vibentec/devJsonFileNames.ts`
- Change the hardcoded `const fileName = jsonFileNames.namVibentec` to read from `process.env.VIBENTEC_DESIGN_CONFIG`.
- Impact: Enables true multi-tenant deployment (different brands on different deployments) without any code changes. Currently requires a code commit to switch brands.
**2. Type the `LayoutContext` properly**
- File: `src/vibentec/component-map.tsx:3438`
- Replace `customer: any` and `cart: any` with `HttpTypes.StoreCustomer | null` and `HttpTypes.StoreCart | null`.
- Impact: Propagates type safety through the entire renderer pipeline, catching prop mismatches at compile time.
**3. Remove dead dependencies**
- `pg` and `@types/pg` — no direct DB access exists in this storefront.
- `@types/react-instantsearch-dom` — no instantsearch code exists.
- Run: `yarn remove pg @types/pg @types/react-instantsearch-dom`
- Impact: Cleaner dependency graph, faster installs, less confusion for new developers.
**4. Fix hardcoded `data-mode="light"`**
- File: `src/app/layout.tsx:11`
- If dark mode is planned: drive this from a cookie or `prefers-color-scheme`. If not: add a comment documenting the intentional lock to light mode.
**5. Delete commented-out V1 cart code**
- File: `src/lib/data/cart.ts:279318`
- The commented blocks are Medusa V1 patterns. They will never be un-commented as-is. Remove them to reduce noise and cognitive load.
---
### 3. Testing Strategy
Given zero existing tests and the RSC-heavy architecture, the recommended approach is **outside-in, starting at the data boundaries**.
**Step 1 — Integration tests for the data layer** (`src/lib/data/`)
These are pure server functions with no React involved — the easiest place to start. Use **Vitest** with a real (or Docker-composed) Medusa backend in test mode.
```
Priority order: cart.ts → customer.ts → products.ts → regions.ts
```
Start with `cart.ts` — it has the most mutations, the most business logic, and already has a confirmed `await` bug. Writing a test for `setAddresses` would have caught that immediately.
**Step 2 — Component tests for the Vibentec renderer** (`src/vibentec/`)
The `DynamicLayoutRenderer` and `componentMap` are the highest-risk custom code with no safety net. Use **React Testing Library** with `@testing-library/react` (RSC-compatible via Next.js jest transform).
Write snapshot tests that pass a known JSON fixture through the full renderer pipeline. This creates a regression guard before any design JSON changes.
```typescript
// Example fixture test shape
it("renders a Header with a Banner child from JSON", () => {
const nodes = [
{ Header: { children: [{ Banner: { config: { variant: "nav" } } }] } },
]
const { container } = render(
<DynamicLayoutRenderer nodes={nodes} context={mockContext} />
)
expect(container).toMatchSnapshot()
})
```
**Step 3 — E2E tests for critical user journeys** using **Playwright**
Two journeys that must never break:
1. `Browse Store → Product Detail → Add to Cart → Checkout → Order Confirmed`
2. `Login → Account Dashboard → View Order History`
**Step 4 — CI enforcement**
The `.github/` directory currently only contains an issue template. Add a GitHub Actions workflow that runs on every PR:
```yaml
- yarn lint # re-enable ignoreDuringBuilds: false first
- tsc --noEmit # re-enable ignoreBuildErrors: false first
- vitest run # data layer integration tests
- playwright test # critical path E2E
```
No PR merges to `main` without all four passing.
+39
View File
@@ -14,6 +14,8 @@
"@radix-ui/react-accordion": "^1.2.1",
"@stripe/react-stripe-js": "^1.7.2",
"@stripe/stripe-js": "^1.29.0",
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"lodash": "^4.17.21",
"next": "^15.3.1",
"pg": "^8.11.3",
@@ -10937,6 +10939,43 @@
"integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==",
"license": "ISC"
},
"node_modules/embla-carousel": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT"
},
"node_modules/embla-carousel-autoplay": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.6.0.tgz",
"integrity": "sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==",
"license": "MIT",
"peerDependencies": {
"embla-carousel": "8.6.0"
}
},
"node_modules/embla-carousel-react": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
"integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
"license": "MIT",
"dependencies": {
"embla-carousel": "8.6.0",
"embla-carousel-reactive-utils": "8.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/embla-carousel-reactive-utils": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
"integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
"license": "MIT",
"peerDependencies": {
"embla-carousel": "8.6.0"
}
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+11 -14
View File
@@ -6,7 +6,7 @@ import SkeletonProductGrid from "@modules/skeletons/templates/skeleton-product-g
import RefinementList from "@modules/store/components/refinement-list"
import { SortOptions } from "@modules/store/components/refinement-list/sort-products"
import PaginatedProducts from "@modules/store/templates/paginated-products"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import Breadcrumb from "@modules/common/components/breadcrumb"
import { HttpTypes } from "@medusajs/types"
export default function CategoryTemplate({
@@ -36,6 +36,15 @@ export default function CategoryTemplate({
getParents(category)
const crumbs = [
{ label: "Home", href: "/" },
...parents.reverse().map((parent) => ({
label: parent.name,
href: `/categories/${parent.handle}`,
})),
{ label: category.name },
]
return (
<div
className="flex flex-col small:flex-row small:items-start py-6 content-container"
@@ -43,20 +52,8 @@ export default function CategoryTemplate({
>
<RefinementList sortBy={sort} data-testid="sort-by-container" />
<div className="w-full">
<Breadcrumb crumbs={crumbs} />
<div className="flex flex-row mb-8 text-2xl-semi gap-4">
{parents &&
parents.map((parent) => (
<span key={parent.id} className="text-ui-fg-subtle">
<LocalizedClientLink
className="mr-4 hover:text-black"
href={`/categories/${parent.handle}`}
data-testid="sort-by-link"
>
{parent.name}
</LocalizedClientLink>
/
</span>
))}
<h1 data-testid="category-page-title">{category.name}</h1>
</div>
{category.description && (
@@ -0,0 +1,55 @@
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import React from "react"
type Crumb = {
label: string
href?: string
}
type BreadcrumbProps = {
crumbs: Crumb[]
}
const Breadcrumb: React.FC<BreadcrumbProps> = ({ crumbs }) => {
const jsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: crumbs.map((crumb, index) => ({
"@type": "ListItem",
position: index + 1,
name: crumb.label,
...(crumb.href ? { item: crumb.href } : {}),
})),
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<nav
className="flex items-center gap-x-2 text-small-regular text-ui-fg-subtle mb-4"
aria-label="Breadcrumb"
>
{crumbs.map((crumb, index) => (
<React.Fragment key={index}>
{index > 0 && <span>/</span>}
{crumb.href ? (
<LocalizedClientLink
href={crumb.href}
className="hover:text-ui-fg-base transition-colors"
>
{crumb.label}
</LocalizedClientLink>
) : (
<span className="text-ui-fg-base">{crumb.label}</span>
)}
</React.Fragment>
))}
</nav>
</>
)
}
export default Breadcrumb
@@ -188,8 +188,8 @@ export default function VtSubcription({
</form>
{classes?.subtextSubcribe && (
<div className={props.subtextSubcribe.className}>
{props.subtextSubcribe.label}
<div className={props.subtextSubcribe?.className}>
{props.subtextSubcribe?.label}
</div>
)}
{submitted && (
+17
View File
@@ -10,6 +10,7 @@ import SkeletonRelatedProducts from "@modules/skeletons/templates/skeleton-relat
import { notFound } from "next/navigation"
import ProductActionsWrapper from "./product-actions-wrapper"
import { HttpTypes } from "@medusajs/types"
import Breadcrumb from "@modules/common/components/breadcrumb"
type ProductTemplateProps = {
product: HttpTypes.StoreProduct
@@ -26,8 +27,24 @@ const ProductTemplate: React.FC<ProductTemplateProps> = ({
return notFound()
}
const category = product.categories?.[0]
const crumbs = [
{ label: "Home", href: "/" },
{ label: "Store", href: "/store" },
...(category
? [{ label: category.name, href: `/categories/${category.handle}` }]
: []),
{ label: product.title },
]
return (
<>
<div
className="content-container py-4"
data-testid="product-breadcrumb"
>
<Breadcrumb crumbs={crumbs} />
</div>
<div
className="content-container flex flex-col small:flex-row small:items-start py-6 relative"
data-testid="product-container"
+23 -266
View File
@@ -308,28 +308,6 @@
dependencies:
tslib "^2.0.0"
"@emnapi/core@^1.4.3":
version "1.5.0"
resolved "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz"
integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==
dependencies:
"@emnapi/wasi-threads" "1.1.0"
tslib "^2.4.0"
"@emnapi/runtime@^1.4.3", "@emnapi/runtime@^1.5.0":
version "1.5.0"
resolved "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz"
integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==
dependencies:
tslib "^2.4.0"
"@emnapi/wasi-threads@1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz"
integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==
dependencies:
tslib "^2.4.0"
"@eslint-community/eslint-utils@^4.7.0":
version "4.9.0"
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz"
@@ -464,72 +442,16 @@
resolved "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz"
integrity sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==
"@img/sharp-darwin-arm64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz"
integrity sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==
optionalDependencies:
"@img/sharp-libvips-darwin-arm64" "1.2.3"
"@img/sharp-darwin-x64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz"
integrity sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==
optionalDependencies:
"@img/sharp-libvips-darwin-x64" "1.2.3"
"@img/sharp-libvips-darwin-arm64@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz"
integrity sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==
"@img/sharp-libvips-darwin-x64@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz"
integrity sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==
"@img/sharp-libvips-linux-arm@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz"
integrity sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==
"@img/sharp-libvips-linux-arm64@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz"
integrity sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==
"@img/sharp-libvips-linux-ppc64@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz"
integrity sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==
"@img/sharp-libvips-linux-s390x@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz"
integrity sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==
"@img/sharp-libvips-linux-x64@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz"
integrity sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==
"@img/sharp-libvips-linuxmusl-arm64@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz"
integrity sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==
"@img/sharp-libvips-linuxmusl-x64@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz"
integrity sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==
"@img/sharp-linux-arm@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz"
integrity sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==
optionalDependencies:
"@img/sharp-libvips-linux-arm" "1.2.3"
"@img/sharp-linux-arm64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz"
@@ -537,27 +459,6 @@
optionalDependencies:
"@img/sharp-libvips-linux-arm64" "1.2.3"
"@img/sharp-linux-ppc64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz"
integrity sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==
optionalDependencies:
"@img/sharp-libvips-linux-ppc64" "1.2.3"
"@img/sharp-linux-s390x@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz"
integrity sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==
optionalDependencies:
"@img/sharp-libvips-linux-s390x" "1.2.3"
"@img/sharp-linux-x64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz"
integrity sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==
optionalDependencies:
"@img/sharp-libvips-linux-x64" "1.2.3"
"@img/sharp-linuxmusl-arm64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz"
@@ -565,35 +466,6 @@
optionalDependencies:
"@img/sharp-libvips-linuxmusl-arm64" "1.2.3"
"@img/sharp-linuxmusl-x64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz"
integrity sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==
optionalDependencies:
"@img/sharp-libvips-linuxmusl-x64" "1.2.3"
"@img/sharp-wasm32@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz"
integrity sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==
dependencies:
"@emnapi/runtime" "^1.5.0"
"@img/sharp-win32-arm64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz"
integrity sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==
"@img/sharp-win32-ia32@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz"
integrity sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==
"@img/sharp-win32-x64@0.34.4":
version "0.34.4"
resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz"
integrity sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==
"@internationalized/date@^3.10.0":
version "3.10.0"
resolved "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz"
@@ -730,15 +602,6 @@
sonner "^1.5.0"
tailwind-merge "^2.2.1"
"@napi-rs/wasm-runtime@^0.2.11":
version "0.2.12"
resolved "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz"
integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==
dependencies:
"@emnapi/core" "^1.4.3"
"@emnapi/runtime" "^1.4.3"
"@tybys/wasm-util" "^0.10.0"
"@next/env@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/env/-/env-15.5.5.tgz"
@@ -751,16 +614,6 @@
dependencies:
fast-glob "3.3.1"
"@next/swc-darwin-arm64@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.5.tgz"
integrity sha512-lYExGHuFIHeOxf40mRLWoA84iY2sLELB23BV5FIDHhdJkN1LpRTPc1MDOawgTo5ifbM5dvAwnGuHyNm60G1+jw==
"@next/swc-darwin-x64@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.5.tgz"
integrity sha512-cacs/WQqa96IhqUm+7CY+z/0j9sW6X80KE07v3IAJuv+z0UNvJtKSlT/T1w1SpaQRa9l0wCYYZlRZUhUOvEVmg==
"@next/swc-linux-arm64-gnu@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.5.tgz"
@@ -771,26 +624,6 @@
resolved "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.5.tgz"
integrity sha512-ekV76G2R/l3nkvylkfy9jBSYHeB4QcJ7LdDseT6INnn1p51bmDS1eGoSoq+RxfQ7B1wt+Qa0pIl5aqcx0GLpbw==
"@next/swc-linux-x64-gnu@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.5.tgz"
integrity sha512-tI+sBu+3FmWtqlqD4xKJcj3KJtqbniLombKTE7/UWyyoHmOyAo3aZ7QcEHIOgInXOG1nt0rwh0KGmNbvSB0Djg==
"@next/swc-linux-x64-musl@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.5.tgz"
integrity sha512-kDRh+epN/ulroNJLr+toDjN+/JClY5L+OAWjOrrKCI0qcKvTw9GBx7CU/rdA2bgi4WpZN3l0rf/3+b8rduEwrQ==
"@next/swc-win32-arm64-msvc@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.5.tgz"
integrity sha512-GDgdNPFFqiKjTrmfw01sMMRWhVN5wOCmFzPloxa7ksDfX6TZt62tAK986f0ZYqWpvDFqeBCLAzmgTURvtQBdgw==
"@next/swc-win32-x64-msvc@15.5.5":
version "15.5.5"
resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.5.tgz"
integrity sha512-5kE3oRJxc7M8RmcTANP8RGoJkaYlwIiDD92gSwCjJY0+j8w8Sl1lvxgQ3bxfHY2KkHFai9tpy/Qx1saWV8eaJQ==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@@ -2765,13 +2598,6 @@
resolved "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz"
integrity sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==
"@tybys/wasm-util@^0.10.0":
version "0.10.1"
resolved "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz"
integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==
dependencies:
tslib "^2.4.0"
"@types/debug@^4.0.0":
version "4.1.12"
resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz"
@@ -3008,41 +2834,6 @@
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
"@unrs/resolver-binding-android-arm-eabi@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz"
integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==
"@unrs/resolver-binding-android-arm64@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz"
integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==
"@unrs/resolver-binding-darwin-arm64@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz"
integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==
"@unrs/resolver-binding-darwin-x64@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz"
integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==
"@unrs/resolver-binding-freebsd-x64@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz"
integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==
"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz"
integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==
"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz"
integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==
"@unrs/resolver-binding-linux-arm64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz"
@@ -3053,58 +2844,6 @@
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz"
integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==
"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz"
integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==
"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz"
integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==
"@unrs/resolver-binding-linux-riscv64-musl@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz"
integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==
"@unrs/resolver-binding-linux-s390x-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz"
integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==
"@unrs/resolver-binding-linux-x64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz"
integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==
"@unrs/resolver-binding-linux-x64-musl@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz"
integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==
"@unrs/resolver-binding-wasm32-wasi@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz"
integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==
dependencies:
"@napi-rs/wasm-runtime" "^0.2.11"
"@unrs/resolver-binding-win32-arm64-msvc@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz"
integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==
"@unrs/resolver-binding-win32-ia32-msvc@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz"
integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==
"@unrs/resolver-binding-win32-x64-msvc@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz"
integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==
"@webassemblyjs/ast@^1.14.1", "@webassemblyjs/ast@1.14.1":
version "1.14.1"
resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz"
@@ -3948,6 +3687,29 @@ electron-to-chromium@^1.5.227:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz"
integrity sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==
embla-carousel-autoplay@^8.6.0:
version "8.6.0"
resolved "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.6.0.tgz"
integrity sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==
embla-carousel-react@^8.6.0:
version "8.6.0"
resolved "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz"
integrity sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==
dependencies:
embla-carousel "8.6.0"
embla-carousel-reactive-utils "8.6.0"
embla-carousel-reactive-utils@8.6.0:
version "8.6.0"
resolved "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz"
integrity sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==
embla-carousel@8.6.0:
version "8.6.0"
resolved "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz"
integrity sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
@@ -4502,11 +4264,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"