Initial commit: Kottgard production website code
@@ -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
|
||||
@@ -0,0 +1,69 @@
|
||||
# Kött Gård — Premium Halal Meat Delivery
|
||||
|
||||
A modern, premium e-commerce website for 100% Halal meat delivery. Built with Next.js, TypeScript, and Tailwind CSS.
|
||||
|
||||
## Features
|
||||
|
||||
- **Premium Design** — Green, white, and gold color palette with luxurious typography
|
||||
- **Multi-language** — Swedish (default), English, and Urdu with RTL support
|
||||
- **Product Customization**
|
||||
- Chicken: Choose quantity in pieces (4, 8, 10, 12, 14)
|
||||
- Beef & Lamb: Choose cutting style (Nihari, Karahi, Qeema, Boneless, Steak)
|
||||
- Fish: Standard professional cut
|
||||
- **Full E-commerce Flow** — Shop, product details, cart, secure checkout
|
||||
- **User Accounts** — Registration, login, order history
|
||||
- **Wishlist** — Save favorite products
|
||||
- **Responsive** — Mobile-first design
|
||||
- **SEO Optimized** — Meta tags, semantic HTML, Open Graph
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
### Demo Account
|
||||
|
||||
- Email: `demo@kottgard.se`
|
||||
- Password: `demo123`
|
||||
|
||||
### Languages
|
||||
|
||||
Use the language bar or globe icon to switch between:
|
||||
- **Svenska** (Swedish) — default
|
||||
- **English**
|
||||
- **اردو** (Urdu) — RTL
|
||||
- **العربية** (Arabic) — RTL
|
||||
- **فارسی** (Persian) — RTL
|
||||
- **Türkçe** (Turkish)
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework:** Next.js 13 (App Router)
|
||||
- **Language:** TypeScript
|
||||
- **Styling:** Tailwind CSS
|
||||
- **State:** Zustand (persisted cart, auth, wishlist, locale)
|
||||
- **i18n:** Custom translation system with locale store
|
||||
- **Icons:** Lucide React
|
||||
|
||||
## Project Structure (Clean Architecture)
|
||||
|
||||
```
|
||||
src/
|
||||
├── domain/ # Entities + business rules (no React/Next)
|
||||
├── application/ # Use cases, ports, container.ts
|
||||
├── infrastructure/ # Zustand, product data, i18n adapters
|
||||
├── presentation/ # useCart, useAuth, useCatalog hooks
|
||||
├── app/ # Next.js pages (thin UI)
|
||||
├── components/ # React UI components
|
||||
├── i18n/ # Translation dictionaries (en, sv, ur)
|
||||
├── hooks/ # useTranslation
|
||||
├── lib/ # Backward-compatible facades
|
||||
├── store/ # Backward-compatible re-exports
|
||||
└── types/ # Re-exports domain entities
|
||||
```
|
||||
|
||||
See **[docs/CLEAN-ARCHITECTURE.md](docs/CLEAN-ARCHITECTURE.md)** for the full step-by-step guide.
|
||||
@@ -0,0 +1,365 @@
|
||||
# Kött Gård — Clean Architecture Guide
|
||||
|
||||
This document explains **what we changed**, **why**, and **how to work in the new structure**.
|
||||
|
||||
---
|
||||
|
||||
## What is Clean Architecture?
|
||||
|
||||
Clean Architecture splits code into **layers**. Inner layers hold business rules. Outer layers hold frameworks (React, Next.js, Zustand, localStorage).
|
||||
|
||||
**Golden rule:** Dependencies point **inward only**.
|
||||
|
||||
```
|
||||
Presentation → Application → Domain
|
||||
Infrastructure → Application → Domain
|
||||
|
||||
Domain imports NOTHING from outside itself.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Before vs After
|
||||
|
||||
### Before (mixed responsibilities)
|
||||
|
||||
```
|
||||
page.tsx → filter products, calculate delivery fee, create orders
|
||||
store/cart.ts → persistence + merge logic + totals
|
||||
lib/products.ts → data + queries in one file
|
||||
```
|
||||
|
||||
Problems:
|
||||
- Delivery fee `total > 500 ? 0 : 49` was copy-pasted in cart **and** checkout
|
||||
- Shop filtering logic lived inside the React page
|
||||
- Hard to swap localStorage for a real API later
|
||||
|
||||
### After (separated layers)
|
||||
|
||||
| Layer | Folder | Job |
|
||||
|-------|--------|-----|
|
||||
| **Domain** | `src/domain/` | Business rules — cart math, delivery fee, order IDs |
|
||||
| **Application** | `src/application/` | Use cases + ports (interfaces) |
|
||||
| **Infrastructure** | `src/infrastructure/` | Zustand, product data file, i18n adapter |
|
||||
| **Presentation** | `src/app/`, `src/components/`, `src/presentation/hooks/` | UI only — calls use cases |
|
||||
|
||||
---
|
||||
|
||||
## Layer diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Presentation
|
||||
P1[app/pages]
|
||||
P2[components]
|
||||
P3[presentation/hooks]
|
||||
end
|
||||
|
||||
subgraph Application
|
||||
A1[use-cases]
|
||||
A2[ports interfaces]
|
||||
A3[container.ts]
|
||||
end
|
||||
|
||||
subgraph Domain
|
||||
D1[entities]
|
||||
D2[domain services]
|
||||
end
|
||||
|
||||
subgraph Infrastructure
|
||||
I1[Zustand stores]
|
||||
I2[InMemoryProductRepository]
|
||||
I3[products.data.ts]
|
||||
end
|
||||
|
||||
P1 --> P3
|
||||
P3 --> A3
|
||||
A3 --> A1
|
||||
A1 --> A2
|
||||
A1 --> D2
|
||||
A2 -.implemented by.- I1
|
||||
A2 -.implemented by.- I2
|
||||
I2 --> I3
|
||||
D2 --> D1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step-by-step: what we did
|
||||
|
||||
### Step 1 — Domain entities (`src/domain/entities/`)
|
||||
|
||||
**What:** Moved `Product`, `CartItem`, `Order`, `User`, etc. from `types/` into the domain.
|
||||
|
||||
**Why:** Entities are the core vocabulary of the business. They must not depend on React or Next.js.
|
||||
|
||||
**Example:** `Product` describes a meat item (price, category, slug) — not how it is rendered.
|
||||
|
||||
```
|
||||
src/domain/entities/index.ts
|
||||
```
|
||||
|
||||
`src/types/index.ts` now **re-exports** domain entities so old imports still work.
|
||||
|
||||
---
|
||||
|
||||
### Step 2 — Domain services (`src/domain/services/`)
|
||||
|
||||
**What:** Pure functions / classes for business rules.
|
||||
|
||||
| Service | Rule extracted from |
|
||||
|---------|---------------------|
|
||||
| `CartDomainService` | `store/cart.ts` — add item, merge duplicates, totals |
|
||||
| `DeliveryFeeService` | `cart/page.tsx` + `checkout/page.tsx` — 49 kr fee, free over 500 kr |
|
||||
| `CustomizationDomainService` | `lib/customization.ts` — cut styles, cart line keys |
|
||||
| `OrderDomainService` | `checkout/page.tsx` — order ID format, order object shape |
|
||||
| `AuthDomainService` | `store/auth.ts` — demo password check, user creation |
|
||||
| `CatalogDomainService` | `shop/page.tsx` — filter by category, search, sort |
|
||||
|
||||
**Why:** One place per rule. Change delivery threshold once → cart and checkout both update.
|
||||
|
||||
**Example — delivery fee (single source of truth):**
|
||||
|
||||
```typescript
|
||||
// src/domain/constants/commerce.ts
|
||||
export const FREE_DELIVERY_THRESHOLD_SEK = 500;
|
||||
export const STANDARD_DELIVERY_FEE_SEK = 49;
|
||||
|
||||
// src/domain/services/DeliveryFeeService.ts
|
||||
static calculateDeliveryFee(subtotal: number): number {
|
||||
return subtotal > FREE_DELIVERY_THRESHOLD_SEK ? 0 : STANDARD_DELIVERY_FEE_SEK;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3 — Application ports (`src/application/ports/`)
|
||||
|
||||
**What:** TypeScript **interfaces** describing what the app needs — not how it is stored.
|
||||
|
||||
| Port | Contract |
|
||||
|------|----------|
|
||||
| `IProductRepository` | `findAll()`, `findBySlug()`, `findFeatured()` |
|
||||
| `ICartRepository` | `getItems()`, `addItem()`, `clearCart()` |
|
||||
| `IAuthRepository` | `login()`, `register()`, `addOrder()` |
|
||||
| `IWishlistRepository` | `toggleItem()`, `isInWishlist()` |
|
||||
| `ITranslationService` | `translate(key)` |
|
||||
|
||||
**Why:** Today products live in a static array. Tomorrow they might come from Shopify or a database. **Only the infrastructure adapter changes** — use cases stay the same.
|
||||
|
||||
---
|
||||
|
||||
### Step 4 — Use cases (`src/application/use-cases/`)
|
||||
|
||||
**What:** One class per user action. Orchestrates domain + ports.
|
||||
|
||||
| Use case | Replaces logic in |
|
||||
|----------|-------------------|
|
||||
| `FilterProductsUseCase` | Shop page filtering/sorting |
|
||||
| `GetProductBySlugUseCase` | `getProductBySlug()` calls |
|
||||
| `LocalizeProductUseCase` | `lib/product-i18n.ts` |
|
||||
| `AddToCartUseCase` | Cart add button |
|
||||
| `GetCartSummaryUseCase` | Cart totals + delivery |
|
||||
| `PlaceOrderUseCase` | Checkout submit handler |
|
||||
| `LoginUseCase` / `RegisterUseCase` | Login page |
|
||||
|
||||
**Example — checkout flow:**
|
||||
|
||||
```
|
||||
User clicks Pay
|
||||
→ PlaceOrderUseCase.execute()
|
||||
→ OrderDomainService.createOrder() [domain]
|
||||
→ authRepository.addOrder() [port]
|
||||
→ cartRepository.clearCart() [port]
|
||||
```
|
||||
|
||||
File: `src/application/use-cases/checkout/PlaceOrder.ts`
|
||||
|
||||
---
|
||||
|
||||
### Step 5 — Infrastructure (`src/infrastructure/`)
|
||||
|
||||
**What:** Concrete implementations of ports + framework code.
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `data/products.data.ts` | Static 16-product catalog |
|
||||
| `repositories/InMemoryProductRepository.ts` | Implements `IProductRepository` |
|
||||
| `persistence/zustand/cartStore.ts` | Zustand + `ZustandCartRepository` |
|
||||
| `persistence/zustand/authStore.ts` | Auth persistence + demo user |
|
||||
| `persistence/zustand/wishlistStore.ts` | Wishlist persistence |
|
||||
| `i18n/I18nTranslationService.ts` | Wraps existing `i18n/` dictionaries |
|
||||
|
||||
**Why Zustand stays:** It is an **infrastructure detail** (browser storage). The application layer only sees `ICartRepository`.
|
||||
|
||||
---
|
||||
|
||||
### Step 6 — Composition root (`src/application/container.ts`)
|
||||
|
||||
**What:** One file that wires everything together.
|
||||
|
||||
```typescript
|
||||
export const container = new ApplicationContainer();
|
||||
// container.addToCart.execute(...)
|
||||
// container.getCartSummary.execute()
|
||||
// container.placeOrder.execute(...)
|
||||
```
|
||||
|
||||
**Why:** Pages and hooks do not `new InMemoryProductRepository()` themselves. That would couple UI to infrastructure. The container is the **only** place that knows concrete classes.
|
||||
|
||||
---
|
||||
|
||||
### Step 7 — Presentation hooks (`src/presentation/hooks/`)
|
||||
|
||||
**What:** React-friendly API on top of the container.
|
||||
|
||||
| Hook | Use in |
|
||||
|------|--------|
|
||||
| `useCart()` | Cart page, Header, product page |
|
||||
| `useAuth()` | Login, account, checkout |
|
||||
| `useWishlist()` | Product card, wishlist page |
|
||||
| `useCheckout()` | Checkout submit |
|
||||
| `useCatalogFilter()` | Shop page |
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
// Old (page knew about Zustand internals + business math)
|
||||
const { getTotal } = useCartStore();
|
||||
const deliveryFee = total > 500 ? 0 : 49;
|
||||
|
||||
// New (page uses use case result)
|
||||
const { summary } = useCart();
|
||||
const { subtotal, deliveryFee, grandTotal } = summary;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 8 — Backward-compatible facades
|
||||
|
||||
Old paths still work so nothing breaks silently:
|
||||
|
||||
| Old import | Now points to |
|
||||
|------------|---------------|
|
||||
| `@/types` | `@/domain/entities` |
|
||||
| `@/lib/customization` | `CustomizationDomainService` |
|
||||
| `@/lib/products` | `container.productRepository` |
|
||||
| `@/store/cart` | infrastructure + `useCart` hook |
|
||||
|
||||
---
|
||||
|
||||
## New folder map
|
||||
|
||||
```
|
||||
src/
|
||||
├── domain/
|
||||
│ ├── entities/ # Product, Order, CartItem…
|
||||
│ ├── constants/ # FREE_DELIVERY_THRESHOLD_SEK
|
||||
│ └── services/ # CartDomainService, DeliveryFeeService…
|
||||
│
|
||||
├── application/
|
||||
│ ├── ports/ # IProductRepository, ICartRepository…
|
||||
│ ├── use-cases/ # AddToCart, PlaceOrder, FilterProducts…
|
||||
│ ├── dtos/ # LocalizedProduct, CartSummary
|
||||
│ └── container.ts # ★ wires everything
|
||||
│
|
||||
├── infrastructure/
|
||||
│ ├── data/ # products.data.ts, demo-orders.data.ts
|
||||
│ ├── repositories/ # InMemoryProductRepository
|
||||
│ ├── persistence/zustand/ # Stores + repository adapters
|
||||
│ └── i18n/ # I18nTranslationService
|
||||
│
|
||||
├── presentation/
|
||||
│ └── hooks/ # useCart, useAuth, useCatalog…
|
||||
│
|
||||
├── app/ # Thin pages (UI + hooks)
|
||||
├── components/ # Visual components
|
||||
├── hooks/useTranslation.ts # Still used for t('key')
|
||||
├── lib/ # Facades (backward compat)
|
||||
├── store/ # Re-exports (backward compat)
|
||||
└── types/ # Re-exports (backward compat)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to add a feature (cheat sheet)
|
||||
|
||||
### Change delivery fee rule
|
||||
1. Edit `src/domain/constants/commerce.ts`
|
||||
2. Done — `GetCartSummaryUseCase` and `PlaceOrderUseCase` pick it up automatically
|
||||
|
||||
### Add a new product
|
||||
1. Add entry in `src/infrastructure/data/products.data.ts`
|
||||
2. Add translations in `src/i18n/locales/*.ts`
|
||||
|
||||
### Add a new page action (e.g. "apply coupon")
|
||||
1. Add rule in `src/domain/services/` if it is business logic
|
||||
2. Add port method if it needs storage
|
||||
3. Create `src/application/use-cases/cart/ApplyCoupon.ts`
|
||||
4. Register in `container.ts`
|
||||
5. Expose via `useCart()` or new hook
|
||||
6. Call from page/component
|
||||
|
||||
### Replace static products with an API
|
||||
1. Create `src/infrastructure/repositories/ApiProductRepository.ts` implementing `IProductRepository`
|
||||
2. In `container.ts`, swap `InMemoryProductRepository` → `ApiProductRepository`
|
||||
3. **No changes** to shop page, use cases, or domain
|
||||
|
||||
---
|
||||
|
||||
## Dependency rules (memorize this)
|
||||
|
||||
| Layer | Can import |
|
||||
|-------|------------|
|
||||
| Domain | Only domain |
|
||||
| Application | Domain + its own ports/DTOs |
|
||||
| Infrastructure | Application ports + Domain |
|
||||
| Presentation | Application container + hooks + React |
|
||||
|
||||
| Layer | Cannot import |
|
||||
|-------|---------------|
|
||||
| Domain | React, Next, Zustand, `app/`, `components/` |
|
||||
| Application | Zustand, `page.tsx`, JSX |
|
||||
| Use cases | Concrete repositories (only interfaces) |
|
||||
|
||||
---
|
||||
|
||||
## Request flow example: Add to cart
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Page as product/page.tsx
|
||||
participant Hook as useCart()
|
||||
participant UC as AddToCartUseCase
|
||||
participant Repo as ZustandCartRepository
|
||||
participant Domain as CartDomainService
|
||||
participant Store as Zustand persist
|
||||
|
||||
Page->>Hook: addItem(product, customization, label)
|
||||
Hook->>UC: execute(...)
|
||||
UC->>Repo: addItem(...)
|
||||
Repo->>Domain: addItem(items, product, ...)
|
||||
Domain-->>Repo: newItems[]
|
||||
Repo->>Store: setItems(newItems)
|
||||
Store-->>Page: React re-render
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verify the project
|
||||
|
||||
```bash
|
||||
cd /Users/apple/Desktop/code/Kottgard
|
||||
npm run build # must pass
|
||||
npm run dev # test at http://localhost:3000
|
||||
```
|
||||
|
||||
Demo login: `demo@kottgard.se` / `demo123`
|
||||
|
||||
---
|
||||
|
||||
## Further reading
|
||||
|
||||
- Uncle Bob — Clean Architecture (concentric circles diagram)
|
||||
- This project's annotated code: `docs/annotated/src/`
|
||||
- Beginner folder map: `~/Desktop/Kottgard-Project-Learn/`
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Generates line-by-line annotated copies of every src/ file.
|
||||
* Output: docs/annotated/src/** (mirrors src/ structure)
|
||||
* Run: node docs/annotate-all.mjs
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const SRC = path.join(ROOT, 'src');
|
||||
const OUT = path.join(__dirname, 'annotated', 'src');
|
||||
|
||||
function explainLine(line, lineNum, relPath) {
|
||||
const t = line.trim();
|
||||
const indent = line.match(/^(\s*)/)[1];
|
||||
|
||||
if (t === '') return `${indent}// (blank line — separates logical blocks for readability)`;
|
||||
if (t.startsWith('/**') || t.startsWith('*') || t.startsWith('*/'))
|
||||
return `${indent}// Block comment — documents the file or function below`;
|
||||
if (t.startsWith('//')) return line;
|
||||
|
||||
if (t === "'use client';" || t === '"use client";')
|
||||
return `${indent}// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)`;
|
||||
if (t.startsWith('import type '))
|
||||
return `${indent}// Type-only import — erased at compile time; no JavaScript bundle cost`;
|
||||
if (t.startsWith('import ')) {
|
||||
if (t.includes("from 'react'") || t.includes('from "react"'))
|
||||
return `${indent}// Import React — core UI library (components, hooks, JSX)`;
|
||||
if (t.includes('next/link'))
|
||||
return `${indent}// Next.js Link — fast client-side navigation without full page reload`;
|
||||
if (t.includes('next/image'))
|
||||
return `${indent}// Next.js Image — optimized images (lazy load, WebP/AVIF)`;
|
||||
if (t.includes('next/navigation'))
|
||||
return `${indent}// Next.js App Router hooks — useRouter, useParams, useSearchParams`;
|
||||
if (t.includes('next/font'))
|
||||
return `${indent}// Self-hosted Google fonts — better performance than external CSS`;
|
||||
if (t.includes('lucide-react'))
|
||||
return `${indent}// Lucide icons — lightweight SVG icon components`;
|
||||
if (t.includes('zustand'))
|
||||
return `${indent}// Zustand — simple global state store (cart, auth, locale)`;
|
||||
if (t.includes('@/'))
|
||||
return `${indent}// Import project module (@/ alias = src/ folder in tsconfig)`;
|
||||
if (t.includes("'./") || t.includes('"./'))
|
||||
return `${indent}// Import from a relative file in the same project`;
|
||||
return `${indent}// Import external package or local module`;
|
||||
}
|
||||
|
||||
if (t.startsWith('export type ') || t.startsWith('export interface '))
|
||||
return `${indent}// Export TypeScript type — defines data shape used across the app`;
|
||||
if (t.startsWith('interface '))
|
||||
return `${indent}// TypeScript interface — contract for object properties and methods`;
|
||||
if (t.startsWith('type '))
|
||||
return `${indent}// TypeScript type alias — union or shorthand for complex types`;
|
||||
if (t.startsWith('export default function'))
|
||||
return `${indent}// Default export — main component/page Next.js or other files import`;
|
||||
if (t.startsWith('export function'))
|
||||
return `${indent}// Named export — utility function other files can import`;
|
||||
if (t.startsWith('export const '))
|
||||
return `${indent}// Named export constant — shared config/data imported elsewhere`;
|
||||
if (t.startsWith('const ') && t.includes('= create<'))
|
||||
return `${indent}// Create Zustand store — global state hook (useXxxStore)`;
|
||||
if (t.startsWith('const ') && t.includes('useState'))
|
||||
return `${indent}// React useState — local component state that triggers re-render on change`;
|
||||
if (t.startsWith('const ') && t.includes('useMemo'))
|
||||
return `${indent}// React useMemo — cache expensive computed value until dependencies change`;
|
||||
if (t.startsWith('const ') && t.includes('useCallback'))
|
||||
return `${indent}// React useCallback — stable function reference for useEffect/useMemo deps`;
|
||||
if (t.startsWith('const ') && t.includes('useEffect'))
|
||||
return `${indent}// React useEffect — run side effect after render (sync URL, redirect, etc.)`;
|
||||
if (t.startsWith('const ') && t.includes('useRouter'))
|
||||
return `${indent}// Next.js router — programmatic navigation (router.push)`;
|
||||
if (t.startsWith('const ') && t.includes('useSearchParams'))
|
||||
return `${indent}// Read URL query string (?category=beef&q=steak)`;
|
||||
if (t.startsWith('const ') && t.includes('useParams'))
|
||||
return `${indent}// Read dynamic route segment ([slug] from URL)`;
|
||||
if (t.startsWith('const ') && t.includes('useTranslation'))
|
||||
return `${indent}// Custom hook — returns t() translator and current locale`;
|
||||
if (t.startsWith('const ') && t.includes('useCartStore') || t.startsWith('const ') && t.includes('useAuthStore') || t.startsWith('const ') && t.includes('useWishlistStore') || t.startsWith('const ') && t.includes('useLocaleStore'))
|
||||
return `${indent}// Zustand selector — subscribe to slice of global store`;
|
||||
if (t.startsWith('function '))
|
||||
return `${indent}// Function declaration — reusable logic in this file`;
|
||||
if (t.startsWith('return ('))
|
||||
return `${indent}// Return JSX — describes UI tree React renders to the DOM`;
|
||||
if (t.startsWith('return '))
|
||||
return `${indent}// Return value from function`;
|
||||
if (t.startsWith('if (') || t.startsWith('} else if ('))
|
||||
return `${indent}// Conditional branch — different behavior based on runtime value`;
|
||||
if (t.startsWith('switch ('))
|
||||
return `${indent}// Switch — multiple branches on one variable (e.g. sort order)`;
|
||||
if (t.startsWith('case '))
|
||||
return `${indent}// Switch case — handle one specific value`;
|
||||
if (t.startsWith('default:'))
|
||||
return `${indent}// Switch default — fallback when no case matches`;
|
||||
if (t.startsWith('useEffect('))
|
||||
return `${indent}// Side effect hook — runs after paint; deps array controls when it re-runs`;
|
||||
if (t.includes('.map('))
|
||||
return `${indent}// Array.map — transform each item (often render a list of components)`;
|
||||
if (t.includes('.filter('))
|
||||
return `${indent}// Array.filter — keep items matching condition (search, category)`;
|
||||
if (t.includes('.sort('))
|
||||
return `${indent}// Array.sort — reorder items (price, name, featured)`;
|
||||
if (t.includes('.find('))
|
||||
return `${indent}// Array.find — get first matching item or undefined`;
|
||||
if (t.includes('.reduce('))
|
||||
return `${indent}// Array.reduce — accumulate single value (cart total, item count)`;
|
||||
if (t.startsWith('<') && !t.startsWith('<>'))
|
||||
return `${indent}// JSX element — HTML-like tag becomes React component in browser`;
|
||||
if (t.startsWith('<>') || t === '</>')
|
||||
return `${indent}// React Fragment — group elements without extra wrapper DOM node`;
|
||||
if (t.startsWith('{/*'))
|
||||
return `${indent}// JSX comment — not visible in browser`;
|
||||
if (t.startsWith('className='))
|
||||
return `${indent}// Tailwind CSS utility classes — styling (colors, spacing, layout)`;
|
||||
if (t.startsWith('href='))
|
||||
return `${indent}// Link target URL — internal route or external https://`;
|
||||
if (t.startsWith('onClick='))
|
||||
return `${indent}// Click handler — runs when user clicks (must be client component)`;
|
||||
if (t.startsWith('onChange='))
|
||||
return `${indent}// Change handler — runs when input/select value changes`;
|
||||
if (t.startsWith('aria-'))
|
||||
return `${indent}// Accessibility attribute — screen readers and assistive tech`;
|
||||
if (t.startsWith('}'))
|
||||
return `${indent}// Closing brace — end of block (function, if, object, JSX)`;
|
||||
if (t.startsWith(']') || t.startsWith('];'))
|
||||
return `${indent}// End of array literal`;
|
||||
if (t.endsWith(',') && !t.includes('//'))
|
||||
return `${indent}// Property or array item — trailing comma allowed in TypeScript`;
|
||||
if (relPath.includes('locales/') && t.match(/^\s{2}\w+:/))
|
||||
return `${indent}// i18n translation section — keys used by t('section.key') in components`;
|
||||
if (relPath.includes('locales/') && t.includes("name:") || relPath.includes('locales/') && t.includes("title:"))
|
||||
return `${indent}// Translated UI string for current language (sv / en / ur)`;
|
||||
if (relPath.includes('products.ts'))
|
||||
return `${indent}// Product catalog entry — demo data shown in shop and product pages`;
|
||||
if (t.includes('persist('))
|
||||
return `${indent}// Zustand persist — save store to localStorage between visits`;
|
||||
|
||||
return `${indent}// Line ${lineNum}: ${t.length > 60 ? t.slice(0, 57) + '...' : t || 'code'}`;
|
||||
}
|
||||
|
||||
function annotateFile(content, relPath) {
|
||||
const lines = content.split('\n');
|
||||
const ext = relPath.endsWith('.tsx') ? 'tsx' : 'ts';
|
||||
const header = [
|
||||
`/**`,
|
||||
` * ANNOTATED COPY — every line explained`,
|
||||
` * Source: src/${relPath}`,
|
||||
` * NOT used by the app — read this to learn how the real file works`,
|
||||
` */`,
|
||||
'',
|
||||
];
|
||||
|
||||
const body = lines.map((line, i) => {
|
||||
const comment = explainLine(line, i + 1, relPath);
|
||||
if (line.trim().startsWith('//') && comment === line) return line;
|
||||
if (line.trim() === '' && comment.includes('blank')) return comment;
|
||||
return `${comment}\n${line}`;
|
||||
});
|
||||
|
||||
return header.join('\n') + body.join('\n') + '\n';
|
||||
}
|
||||
|
||||
function walk(dir, base = '') {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
let count = 0;
|
||||
for (const e of entries) {
|
||||
const rel = base ? `${base}/${e.name}` : e.name;
|
||||
const full = path.join(dir, e.name);
|
||||
if (e.isDirectory()) {
|
||||
count += walk(full, rel);
|
||||
} else if (/\.(ts|tsx)$/.test(e.name)) {
|
||||
const content = fs.readFileSync(full, 'utf8');
|
||||
const annotated = annotateFile(content, rel);
|
||||
const outName = e.name.replace(/\.(tsx?)$/, '.annotated.$1');
|
||||
const outDir = path.join(OUT, base);
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(outDir, outName), annotated);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Remove old flat annotated files (replaced by src/ mirror)
|
||||
const oldFlat = path.join(__dirname, 'annotated');
|
||||
for (const f of fs.readdirSync(oldFlat)) {
|
||||
if (f.endsWith('.annotated.ts') || f.endsWith('.annotated.tsx')) {
|
||||
if (!f.startsWith('0')) continue;
|
||||
try { fs.unlinkSync(path.join(oldFlat, f)); } catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
const n = walk(SRC);
|
||||
console.log(`Annotated ${n} files → docs/annotated/src/`);
|
||||
@@ -0,0 +1,63 @@
|
||||
# Annotated Source Code — Every Line Explained
|
||||
|
||||
This folder contains **annotated copies** of **every** file in `src/`. Each line of the original code is followed or preceded by a comment explaining:
|
||||
|
||||
- **What** the syntax means (TypeScript, React, Next.js)
|
||||
- **Why** it exists in the Kött Gård project
|
||||
|
||||
Production code in `src/` stays clean. Learning comments live here only.
|
||||
|
||||
## Regenerate all annotations
|
||||
|
||||
```bash
|
||||
node docs/annotate-all.mjs
|
||||
```
|
||||
|
||||
Run this after you change source files to refresh the annotated copies.
|
||||
|
||||
## Folder mirror
|
||||
|
||||
```
|
||||
docs/annotated/src/ ← you are here (annotated)
|
||||
src/ ← real app code (no line comments)
|
||||
```
|
||||
|
||||
| Annotated path | Original path |
|
||||
|----------------|---------------|
|
||||
| `docs/annotated/src/app/page.annotated.tsx` | `src/app/page.tsx` |
|
||||
| `docs/annotated/src/lib/products.annotated.ts` | `src/lib/products.ts` |
|
||||
| `docs/annotated/src/store/cart.annotated.ts` | `src/store/cart.ts` |
|
||||
| … | (52 files total) |
|
||||
|
||||
## File categories
|
||||
|
||||
| Folder | What it contains |
|
||||
|--------|------------------|
|
||||
| `app/` | Pages and routes (Next.js App Router) |
|
||||
| `components/` | Reusable UI (home sections, layout, product cards) |
|
||||
| `lib/` | Data, images, constants, helpers |
|
||||
| `store/` | Zustand global state (cart, auth, wishlist, locale) |
|
||||
| `i18n/` | Translations (Swedish, English, Urdu) |
|
||||
| `hooks/` | Custom React hooks |
|
||||
| `types/` | TypeScript interfaces |
|
||||
|
||||
## How to read
|
||||
|
||||
1. Open the **original** file in `src/` in your editor.
|
||||
2. Open the matching **`.annotated.ts`** or **`.annotated.tsx`** file side-by-side.
|
||||
3. Read the gray `//` comment above each line, then the code.
|
||||
|
||||
## Programming languages in this project
|
||||
|
||||
| Language | Role |
|
||||
|----------|------|
|
||||
| **TypeScript** | Types catch errors before run; interfaces for Product, Cart, Order |
|
||||
| **React 18** | Components + JSX UI; hooks for state and effects |
|
||||
| **Next.js 13** | File-based routing, layouts, metadata, image optimization |
|
||||
| **Tailwind CSS** | Utility classes for burgundy/cream/gold design |
|
||||
| **Zustand** | Global stores with `persist` → localStorage |
|
||||
|
||||
## Related documents
|
||||
|
||||
- Word guide: `docs/Kottgard-Website-Guide.docx`
|
||||
- Visual guide: `../kottgard documentation/16-website-visual-and-code-guide.md`
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/about/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ShieldCheck, Leaf, Award, Truck, Phone, Mail, MapPin } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import external package or local module
|
||||
import {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_ADDRESS,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_EMAIL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_PHONE_DISPLAY,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_HOURS,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} from '@/lib/constants';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function AboutPage() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// Line 15: const telHref = `tel:${SITE_PHONE_DISPLAY.replace(/\s/g, ...
|
||||
const telHref = `tel:${SITE_PHONE_DISPLAY.replace(/\s/g, '')}`;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="bg-gradient-to-br from-brand-900 to-brand-950 px-4 py-20 text-white sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="mb-4 font-display text-4xl font-bold">
|
||||
// Line 22: {t('about.title', { name: t('site.name') })}
|
||||
{t('about.title', { name: t('site.name') })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-lg text-brand-100">{t('about.subtitle')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-4xl px-4 py-16 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="mb-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-4">{t('about.ourStory')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-4 leading-relaxed text-gray-600">
|
||||
// Line 32: {t('about.storyP1', { name: t('site.name') })}
|
||||
{t('about.storyP1', { name: t('site.name') })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="leading-relaxed text-gray-600">{t('about.storyP2')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section id="halal" className="mb-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShieldCheck className="h-8 w-8 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading">{t('about.halalTitle')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="leading-relaxed text-gray-600">
|
||||
// Line 43: {t('about.halalDesc', { name: t('site.name') })}
|
||||
{t('about.halalDesc', { name: t('site.name') })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="mb-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-6 sm:grid-cols-3">
|
||||
// Line 49: {[
|
||||
{[
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ icon: Leaf, title: t('about.freshDaily'), desc: t('about.freshDailyDesc') },
|
||||
// Line 51: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: Award,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('about.premiumQuality'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
desc: t('about.premiumQualityDesc'),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 56: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: Truck,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('about.fastDelivery'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
desc: t('about.fastDeliveryDesc'),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
].map((item) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div key={item.title} className="card-premium p-6 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<item.icon className="mx-auto mb-3 h-8 w-8 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-1 font-semibold text-brand-900">{item.title}</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm text-gray-500">{item.desc}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 67: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section id="delivery" className="mb-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-4">{t('about.deliveryTitle')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-3 text-gray-600">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p>{t('about.delivery1')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p>{t('about.delivery2')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p>{t('about.delivery3')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p>{t('about.delivery4')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section id="contact" className="card-premium mb-16 p-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-6">{t('about.contactTitle')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-3 text-gray-600">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Phone className="h-5 w-5 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={telHref} className="hover:text-brand-800">
|
||||
// Line 87: {SITE_PHONE_DISPLAY}
|
||||
{SITE_PHONE_DISPLAY}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-3 text-gray-600">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Mail className="h-5 w-5 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={`mailto:${SITE_EMAIL}`} className="hover:text-brand-800">
|
||||
// Line 93: {SITE_EMAIL}
|
||||
{SITE_EMAIL}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-start gap-3 text-gray-600">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MapPin className="mt-0.5 h-5 w-5 shrink-0 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>
|
||||
// Line 99: {SITE_ADDRESS}
|
||||
{SITE_ADDRESS}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<br />
|
||||
// Line 101: {t('footer.hours', { hours: SITE_HOURS })}
|
||||
{t('footer.hours', { hours: SITE_HOURS })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mt-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/shop"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="text-sm font-semibold text-brand-700 hover:text-brand-900"
|
||||
// Line 109: >
|
||||
>
|
||||
// Line 110: {t('cta.button')} →
|
||||
{t('cta.button')} →
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section id="privacy" className="mb-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-4">{t('about.privacyTitle')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="leading-relaxed text-gray-600">{t('about.privacyText')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section id="terms" className="mb-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-4">{t('about.termsTitle')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="leading-relaxed text-gray-600">{t('about.termsText')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 126: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/account/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useEffect } from 'react';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Next.js App Router hooks — useRouter, useParams, useSearchParams
|
||||
import { useRouter } from 'next/navigation';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { User, Package, MapPin, Phone, Mail, LogOut, Heart } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { formatPrice, formatDate, getFormatLocale } from '@/lib/utils';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function AccountPage() {
|
||||
// Next.js router — programmatic navigation (router.push)
|
||||
const router = useRouter();
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { user, isAuthenticated, orders, logout } = useAuthStore();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t, locale } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Side effect hook — runs after paint; deps array controls when it re-runs
|
||||
useEffect(() => {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (!isAuthenticated) {
|
||||
// Line 20: router.push('/login');
|
||||
router.push('/login');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}, [isAuthenticated, router]);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (!isAuthenticated || !user) {
|
||||
// Return value from function
|
||||
return null;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 28: const handleLogout = () => {
|
||||
const handleLogout = () => {
|
||||
// Line 29: logout();
|
||||
logout();
|
||||
// Line 30: router.push('/');
|
||||
router.push('/');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 33: const fmt = (n: number) => formatPrice(n, getFormatLocale...
|
||||
const fmt = (n: number) => formatPrice(n, getFormatLocale(locale));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="bg-gray-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="border-b border-gray-100 bg-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="section-heading">{t('account.title')}</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-gray-500">{t('account.welcome', { name: user.name })}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-8 lg:grid-cols-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-brand-100">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<User className="h-6 w-6 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="font-display text-lg font-semibold">{user.name}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm text-gray-500">
|
||||
// Line 55: {t('account.memberSince', { date: formatDate(user.created...
|
||||
{t('account.memberSince', { date: formatDate(user.createdAt) })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-3 border-t border-gray-100 pt-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Mail className="h-4 w-4 text-brand-600" />
|
||||
// Line 63: {user.email}
|
||||
{user.email}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Phone className="h-4 w-4 text-brand-600" />
|
||||
// Line 67: {user.phone}
|
||||
{user.phone}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-start gap-2 text-sm text-gray-600">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MapPin className="mt-0.5 h-4 w-4 shrink-0 text-brand-600" />
|
||||
// Line 71: {user.address.street}, {user.address.city}, {user.address...
|
||||
{user.address.street}, {user.address.city}, {user.address.state}{' '}
|
||||
// Line 72: {user.address.zip}
|
||||
{user.address.zip}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium p-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/wishlist"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-brand-50 hover:text-brand-700"
|
||||
// Line 81: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Heart className="h-4 w-4" />
|
||||
// Line 83: {t('account.myWishlist')}
|
||||
{t('account.myWishlist')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={handleLogout}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-red-500 transition-colors hover:bg-red-50"
|
||||
// Line 88: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<LogOut className="h-4 w-4" />
|
||||
// Line 90: {t('account.signOut')}
|
||||
{t('account.signOut')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="lg:col-span-2" id="orders">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Package className="h-5 w-5 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="font-display text-lg font-semibold">
|
||||
// Line 100: {t('account.orderHistory')}
|
||||
{t('account.orderHistory')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 104: {orders.length === 0 ? (
|
||||
{orders.length === 0 ? (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="py-12 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-2 text-gray-500">{t('account.noOrders')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="primary">{t('account.startShopping')}</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 111: ) : (
|
||||
) : (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-4">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{orders.map((order) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div
|
||||
// Line 115: key={order.id}
|
||||
key={order.id}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-xl border border-gray-100 p-4 transition-colors hover:border-brand-200"
|
||||
// Line 117: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm font-semibold text-brand-900">{order.id}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-gray-400">
|
||||
// Line 122: {formatDate(order.createdAt)}
|
||||
{formatDate(order.createdAt)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="text-end">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="font-bold text-brand-800">{fmt(order.total)}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="inline-block rounded-full bg-brand-50 px-2 py-0.5 text-xs font-medium capitalize text-brand-700">
|
||||
// Line 128: {t(`orderStatus.${order.status}`)}
|
||||
{t(`orderStatus.${order.status}`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-1">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{order.items.map((item) => {
|
||||
// Line 134: const localized = localizeProduct(item.product, t);
|
||||
const localized = localizeProduct(item.product, t);
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div
|
||||
// Line 137: key={item.id}
|
||||
key={item.id}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex justify-between text-sm text-gray-600"
|
||||
// Line 139: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={`/product/${item.product.slug}`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="hover:text-brand-700"
|
||||
// Line 143: >
|
||||
>
|
||||
// Line 144: {localized.name} × {item.quantity}
|
||||
{localized.name} × {item.quantity}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="ms-2 text-xs text-gold-600">
|
||||
// Line 146: ({item.customizationLabel})
|
||||
({item.customizationLabel})
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>{fmt(item.product.price * item.quantity)}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 151: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
})}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 155: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 157: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 163: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/cart/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Minus, Plus, Trash2, ShoppingBag, ArrowRight } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useCartStore } from '@/store/cart';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { formatPrice, getFormatLocale } from '@/lib/utils';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function CartPage() {
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { items, updateQuantity, removeItem, getTotal } = useCartStore();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t, locale } = useTranslation();
|
||||
// Line 15: const total = getTotal();
|
||||
const total = getTotal();
|
||||
// Line 16: const deliveryFee = total > 500 ? 0 : 49;
|
||||
const deliveryFee = total > 500 ? 0 : 49;
|
||||
// Line 17: const grandTotal = total + deliveryFee;
|
||||
const grandTotal = total + deliveryFee;
|
||||
// Line 18: const fmt = (n: number) => formatPrice(n, getFormatLocale...
|
||||
const fmt = (n: number) => formatPrice(n, getFormatLocale(locale));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (items.length === 0) {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-20 text-center sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-brand-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShoppingBag className="h-10 w-10 text-brand-300" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="mb-2 font-display text-2xl font-bold text-brand-900">
|
||||
// Line 27: {t('cart.empty')}
|
||||
{t('cart.empty')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-8 text-gray-500">{t('cart.emptyHint')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="primary" size="lg">
|
||||
// Line 32: {t('cart.startShopping')}
|
||||
{t('cart.startShopping')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 36: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="bg-gray-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="border-b border-gray-100 bg-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="section-heading">{t('cart.title')}</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-gray-500">
|
||||
// Line 45: {t('cart.itemsCount', { count: items.length })}
|
||||
{t('cart.itemsCount', { count: items.length })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-8 lg:grid-cols-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-4 lg:col-span-2">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{items.map((item) => {
|
||||
// Line 54: const localized = localizeProduct(item.product, t);
|
||||
const localized = localizeProduct(item.product, t);
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div key={item.id} className="card-premium flex gap-4 p-4 sm:p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative h-24 w-24 shrink-0 overflow-hidden rounded-xl sm:h-28 sm:w-28">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 59: src={item.product.image}
|
||||
src={item.product.image}
|
||||
// Line 60: alt={localized.name}
|
||||
alt={localized.name}
|
||||
// Line 61: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover"
|
||||
// Line 63: sizes="(max-width: 640px) 96px, 112px"
|
||||
sizes="(max-width: 640px) 96px, 112px"
|
||||
// Line 64: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-1 flex-col">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-start justify-between">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={`/product/${item.product.slug}`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="font-display text-lg font-semibold text-brand-900 hover:text-brand-700"
|
||||
// Line 73: >
|
||||
>
|
||||
// Line 74: {localized.name}
|
||||
{localized.name}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-1 text-xs uppercase tracking-wider text-brand-600">
|
||||
// Line 77: {t(`categories.${item.product.category}.name`)}
|
||||
{t(`categories.${item.product.category}.name`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => removeItem(item.id)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-lg p-2 text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('cart.removeItem')}
|
||||
// Line 84: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Trash2 className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mt-2 inline-flex items-center gap-1.5 rounded-full bg-gold-50 px-3 py-1">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-xs font-medium text-gold-700">
|
||||
// Line 91: {t('cart.customization')}
|
||||
{t('cart.customization')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-xs font-semibold text-gold-800">
|
||||
// Line 94: {item.customizationLabel}
|
||||
{item.customizationLabel}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mt-auto flex items-center justify-between pt-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center rounded-lg border border-gray-200">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => updateQuantity(item.id, item.quantity - 1)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex h-9 w-9 items-center justify-center text-gray-500 hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('product.decreaseQty')}
|
||||
// Line 104: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Minus className="h-3 w-3" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="w-8 text-center text-sm font-semibold">
|
||||
// Line 108: {item.quantity}
|
||||
{item.quantity}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => updateQuantity(item.id, item.quantity + 1)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex h-9 w-9 items-center justify-center text-gray-500 hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('product.increaseQty')}
|
||||
// Line 114: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Plus className="h-3 w-3" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-lg font-bold text-brand-800">
|
||||
// Line 119: {fmt(item.product.price * item.quantity)}
|
||||
{fmt(item.product.price * item.quantity)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 124: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
})}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium sticky top-24 p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="mb-4 font-display text-lg font-semibold text-brand-900">
|
||||
// Line 131: {t('cart.orderSummary')}
|
||||
{t('cart.orderSummary')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-3 border-b border-gray-100 pb-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex justify-between text-sm">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-gray-500">{t('cart.subtotal')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="font-medium">{fmt(total)}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex justify-between text-sm">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-gray-500">{t('cart.delivery')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="font-medium">
|
||||
// Line 142: {deliveryFee === 0 ? (
|
||||
{deliveryFee === 0 ? (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-brand-700">{t('cart.free')}</span>
|
||||
// Line 144: ) : (
|
||||
) : (
|
||||
// Line 145: fmt(deliveryFee)
|
||||
fmt(deliveryFee)
|
||||
// Line 146: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 149: {deliveryFee > 0 && (
|
||||
{deliveryFee > 0 && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-gray-400">{t('cart.freeDeliveryHint')}</p>
|
||||
// Line 151: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex justify-between py-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="font-semibold text-brand-900">{t('cart.total')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-xl font-bold text-brand-800">{fmt(grandTotal)}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/checkout">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="gold" size="lg" className="w-full">
|
||||
// Line 161: {t('cart.proceedCheckout')}
|
||||
{t('cart.proceedCheckout')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/shop"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="mt-3 block text-center text-sm font-medium text-brand-600 hover:text-brand-800"
|
||||
// Line 169: >
|
||||
>
|
||||
// Line 170: {t('cart.continueShopping')}
|
||||
{t('cart.continueShopping')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 177: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,701 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/checkout/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useState } from 'react';
|
||||
// Next.js App Router hooks — useRouter, useParams, useSearchParams
|
||||
import { useRouter } from 'next/navigation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Lock, CreditCard, Truck, CheckCircle, ChevronLeft } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useCartStore } from '@/store/cart';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { formatPrice, getFormatLocale } from '@/lib/utils';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Order } from '@/types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function CheckoutPage() {
|
||||
// Next.js router — programmatic navigation (router.push)
|
||||
const router = useRouter();
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { items, getTotal, clearCart } = useCartStore();
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { user, isAuthenticated, addOrder } = useAuthStore();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t, locale } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [paymentMethod, setPaymentMethod] = useState('card');
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [orderComplete, setOrderComplete] = useState(false);
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [orderId, setOrderId] = useState('');
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [form, setForm] = useState({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: user?.name || '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: user?.email || '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: user?.phone || '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: user?.address.street || '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: user?.address.city || '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: user?.address.state || '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: user?.address.zip || '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cardNumber: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
expiry: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cvv: '',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 40: const total = getTotal();
|
||||
const total = getTotal();
|
||||
// Line 41: const deliveryFee = total > 500 ? 0 : 49;
|
||||
const deliveryFee = total > 500 ? 0 : 49;
|
||||
// Line 42: const grandTotal = total + deliveryFee;
|
||||
const grandTotal = total + deliveryFee;
|
||||
// Line 43: const fmt = (n: number) => formatPrice(n, getFormatLocale...
|
||||
const fmt = (n: number) => formatPrice(n, getFormatLocale(locale));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (items.length === 0 && !orderComplete) {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-20 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="mb-4 font-display text-2xl font-bold">{t('checkout.noItems')}</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button>{t('checkout.goToShop')}</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 53: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (orderComplete) {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-lg px-4 py-20 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-brand-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<CheckCircle className="h-12 w-12 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="mb-2 font-display text-3xl font-bold text-brand-900">
|
||||
// Line 63: {t('checkout.orderConfirmed')}
|
||||
{t('checkout.orderConfirmed')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-2 text-gray-500">{t('checkout.thankYou')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-8 text-sm font-medium text-brand-700">
|
||||
// Line 67: {t('checkout.orderId', { id: orderId })}
|
||||
{t('checkout.orderId', { id: orderId })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:justify-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/account">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="primary">{t('checkout.viewOrders')}</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="secondary">{t('cart.continueShopping')}</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 78: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 81: const handleSubmit = async (e: React.FormEvent) => {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
// Line 82: e.preventDefault();
|
||||
e.preventDefault();
|
||||
// Line 83: setIsProcessing(true);
|
||||
setIsProcessing(true);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 85: await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 87: const newOrderId = `KG-${Date.now().toString(36).toUpperC...
|
||||
const newOrderId = `KG-${Date.now().toString(36).toUpperCase()}`;
|
||||
// Line 88: const order: Order = {
|
||||
const order: Order = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: newOrderId,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
items: [...items],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
total: grandTotal,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
status: 'confirmed',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createdAt: new Date().toISOString(),
|
||||
// Line 94: deliveryAddress: {
|
||||
deliveryAddress: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: form.street,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: form.city,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: form.state,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: form.zip,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
paymentMethod,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 103: addOrder(order);
|
||||
addOrder(order);
|
||||
// Line 104: clearCart();
|
||||
clearCart();
|
||||
// Line 105: setOrderId(newOrderId);
|
||||
setOrderId(newOrderId);
|
||||
// Line 106: setOrderComplete(true);
|
||||
setOrderComplete(true);
|
||||
// Line 107: setIsProcessing(false);
|
||||
setIsProcessing(false);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 110: const updateField = (field: string, value: string) => {
|
||||
const updateField = (field: string, value: string) => {
|
||||
// Line 111: setForm((prev) => ({ ...prev, [field]: value }));
|
||||
setForm((prev) => ({ ...prev, [field]: value }));
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="bg-gray-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/cart"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="mb-6 inline-flex items-center gap-1 text-sm font-medium text-gray-500 hover:text-brand-700"
|
||||
// Line 120: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ChevronLeft className="h-4 w-4 rtl:rotate-180" />
|
||||
// Line 122: {t('checkout.backToCart')}
|
||||
{t('checkout.backToCart')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="section-heading mb-8">{t('checkout.title')}</h1>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<form onSubmit={handleSubmit}>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-8 lg:grid-cols-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-6 lg:col-span-2">
|
||||
// Line 130: {!isAuthenticated && (
|
||||
{!isAuthenticated && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm text-gray-600">
|
||||
// Line 133: {t('checkout.haveAccount')}{' '}
|
||||
{t('checkout.haveAccount')}{' '}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/login" className="font-semibold text-brand-700 hover:underline">
|
||||
// Line 135: {t('checkout.signIn')}
|
||||
{t('checkout.signIn')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>{' '}
|
||||
// Line 137: {t('checkout.fasterCheckout')}
|
||||
{t('checkout.fasterCheckout')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 140: )}
|
||||
)}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Truck className="h-5 w-5 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="font-display text-lg font-semibold">
|
||||
// Line 146: {t('checkout.deliveryDetails')}
|
||||
{t('checkout.deliveryDetails')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="sm:col-span-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.fullName')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 153: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 155: value={form.name}
|
||||
value={form.name}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('name', e.target.value)}
|
||||
// Line 157: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.email')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 162: required
|
||||
required
|
||||
// Line 163: type="email"
|
||||
type="email"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 165: value={form.email}
|
||||
value={form.email}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('email', e.target.value)}
|
||||
// Line 167: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.phone')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 172: required
|
||||
required
|
||||
// Line 173: type="tel"
|
||||
type="tel"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 175: value={form.phone}
|
||||
value={form.phone}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('phone', e.target.value)}
|
||||
// Line 177: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="sm:col-span-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.street')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 182: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 184: value={form.street}
|
||||
value={form.street}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('street', e.target.value)}
|
||||
// Line 186: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.city')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 191: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 193: value={form.city}
|
||||
value={form.city}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('city', e.target.value)}
|
||||
// Line 195: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.state')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 200: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 202: value={form.state}
|
||||
value={form.state}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('state', e.target.value)}
|
||||
// Line 204: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.zip')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 209: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 211: value={form.zip}
|
||||
value={form.zip}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('zip', e.target.value)}
|
||||
// Line 213: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<CreditCard className="h-5 w-5 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="font-display text-lg font-semibold">{t('checkout.payment')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-4 flex gap-3">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{['card', 'cod'].map((method) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 227: key={method}
|
||||
key={method}
|
||||
// Line 228: type="button"
|
||||
type="button"
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setPaymentMethod(method)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={`rounded-lg border-2 px-4 py-2 text-sm font-medium transition-all ${
|
||||
// Line 231: paymentMethod === method
|
||||
paymentMethod === method
|
||||
// Line 232: ? 'border-brand-700 bg-brand-700 text-white'
|
||||
? 'border-brand-700 bg-brand-700 text-white'
|
||||
// Line 233: : 'border-gray-200 text-gray-600 hover:border-brand-300'
|
||||
: 'border-gray-200 text-gray-600 hover:border-brand-300'
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}`}
|
||||
// Line 235: >
|
||||
>
|
||||
// Line 236: {method === 'card'
|
||||
{method === 'card'
|
||||
// Line 237: ? t('checkout.creditCard')
|
||||
? t('checkout.creditCard')
|
||||
// Line 238: : t('checkout.cashOnDelivery')}
|
||||
: t('checkout.cashOnDelivery')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 240: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 243: {paymentMethod === 'card' && (
|
||||
{paymentMethod === 'card' && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="sm:col-span-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.cardNumber')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 248: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 250: placeholder="1234 5678 9012 3456"
|
||||
placeholder="1234 5678 9012 3456"
|
||||
// Line 251: value={form.cardNumber}
|
||||
value={form.cardNumber}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('cardNumber', e.target.value)}
|
||||
// Line 253: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.expiry')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 258: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 260: placeholder="MM/YY"
|
||||
placeholder="MM/YY"
|
||||
// Line 261: value={form.expiry}
|
||||
value={form.expiry}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('expiry', e.target.value)}
|
||||
// Line 263: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('checkout.cvv')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 268: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 270: placeholder="123"
|
||||
placeholder="123"
|
||||
// Line 271: value={form.cvv}
|
||||
value={form.cvv}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => updateField('cvv', e.target.value)}
|
||||
// Line 273: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 276: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium sticky top-24 p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="mb-4 font-display text-lg font-semibold">
|
||||
// Line 283: {t('cart.orderSummary')}
|
||||
{t('cart.orderSummary')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-4 max-h-60 space-y-3 overflow-y-auto">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{items.map((item) => {
|
||||
// Line 288: const localized = localizeProduct(item.product, t);
|
||||
const localized = localizeProduct(item.product, t);
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div key={item.id} className="flex gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative h-12 w-12 shrink-0 overflow-hidden rounded-lg">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 293: src={item.product.image}
|
||||
src={item.product.image}
|
||||
// Line 294: alt={localized.name}
|
||||
alt={localized.name}
|
||||
// Line 295: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover"
|
||||
// Line 297: sizes="48px"
|
||||
sizes="48px"
|
||||
// Line 298: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex-1">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm font-medium text-brand-900">
|
||||
// Line 302: {localized.name}
|
||||
{localized.name}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-gold-700">
|
||||
// Line 305: {item.customizationLabel}
|
||||
{item.customizationLabel}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-gray-400">
|
||||
// Line 308: {t('checkout.qty', { count: item.quantity })}
|
||||
{t('checkout.qty', { count: item.quantity })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-sm font-medium">
|
||||
// Line 312: {fmt(item.product.price * item.quantity)}
|
||||
{fmt(item.product.price * item.quantity)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 315: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
})}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-2 border-t border-gray-100 pt-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex justify-between text-sm">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-gray-500">{t('cart.subtotal')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>{fmt(total)}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex justify-between text-sm">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-gray-500">{t('cart.delivery')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>
|
||||
// Line 327: {deliveryFee === 0 ? t('cart.free') : fmt(deliveryFee)}
|
||||
{deliveryFee === 0 ? t('cart.free') : fmt(deliveryFee)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex justify-between pt-2 text-lg font-bold">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>{t('cart.total')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-brand-800">{fmt(grandTotal)}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button
|
||||
// Line 337: type="submit"
|
||||
type="submit"
|
||||
// Line 338: variant="gold"
|
||||
variant="gold"
|
||||
// Line 339: size="lg"
|
||||
size="lg"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="mt-6 w-full"
|
||||
// Line 341: disabled={isProcessing}
|
||||
disabled={isProcessing}
|
||||
// Line 342: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Lock className="h-4 w-4" />
|
||||
// Line 344: {isProcessing
|
||||
{isProcessing
|
||||
// Line 345: ? t('checkout.processing')
|
||||
? t('checkout.processing')
|
||||
// Line 346: : t('checkout.pay', { amount: fmt(grandTotal) })}
|
||||
: t('checkout.pay', { amount: fmt(grandTotal) })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-3 flex items-center justify-center gap-1 text-xs text-gray-400">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Lock className="h-3 w-3" />
|
||||
// Line 351: {t('checkout.secure')}
|
||||
{t('checkout.secure')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</form>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 359: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/layout.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Type-only import — erased at compile time; no JavaScript bundle cost
|
||||
import type { Metadata } from 'next';
|
||||
// Self-hosted Google fonts — better performance than external CSS
|
||||
import { Inter, Playfair_Display, Noto_Nastaliq_Urdu } from 'next/font/google';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import LanguageBanner from '@/components/layout/LanguageBanner';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Header from '@/components/layout/Header';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Footer from '@/components/layout/Footer';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import LocaleAttributes from '@/components/layout/LocaleAttributes';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { SITE_NAME } from '@/lib/constants';
|
||||
// Import from a relative file in the same project
|
||||
import './globals.css';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 10: const inter = Inter({
|
||||
const inter = Inter({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subsets: ['latin'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
variable: '--font-inter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
display: 'swap',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 16: const playfair = Playfair_Display({
|
||||
const playfair = Playfair_Display({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subsets: ['latin'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
variable: '--font-playfair',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
display: 'swap',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 22: const notoUrdu = Noto_Nastaliq_Urdu({
|
||||
const notoUrdu = Noto_Nastaliq_Urdu({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subsets: ['arabic'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
variable: '--font-urdu',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: ['400', '500', '600', '700'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
display: 'swap',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 29: const siteDescription =
|
||||
const siteDescription =
|
||||
// Line 30: 'Premium 100% Halal meat delivery. Fresh and frozen chick...
|
||||
'Premium 100% Halal meat delivery. Fresh and frozen chicken, beef, lamb, and fish — customized to your preference and delivered to your door.';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const viewport = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
themeColor: '#8B1F1F',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const metadata: Metadata = {
|
||||
// Line 37: title: {
|
||||
title: {
|
||||
// Switch default — fallback when no case matches
|
||||
default: `${SITE_NAME} — Premium Halal Meat Delivery`,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
template: `%s | ${SITE_NAME}`,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: siteDescription,
|
||||
// Line 42: keywords: [
|
||||
keywords: [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'halal meat',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'halal chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'halal beef',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'halal lamb',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'meat delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'fresh meat',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Kött Gård',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'kottgard',
|
||||
// End of array literal
|
||||
],
|
||||
// Line 52: openGraph: {
|
||||
openGraph: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: `${SITE_NAME} — Premium Halal Meat Delivery`,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: siteDescription,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
type: 'website',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
locale: 'sv_SE',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
siteName: SITE_NAME,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 59: robots: {
|
||||
robots: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
index: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
follow: true,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function RootLayout({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
children,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}: {
|
||||
// Line 68: children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}) {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<html
|
||||
// Line 72: lang="sv"
|
||||
lang="sv"
|
||||
// Line 73: dir="ltr"
|
||||
dir="ltr"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={`${inter.variable} ${playfair.variable} ${notoUrdu.variable}`}
|
||||
// Line 75: suppressHydrationWarning
|
||||
suppressHydrationWarning
|
||||
// Line 76: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<body className="flex min-h-screen flex-col font-sans">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<LocaleAttributes />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="sticky top-0 z-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<LanguageBanner />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Header />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<main className="flex-1">{children}</main>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Footer />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</body>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</html>
|
||||
// Line 87: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/login/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useState } from 'react';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Next.js App Router hooks — useRouter, useParams, useSearchParams
|
||||
import { useRouter } from 'next/navigation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { DEMO_EMAIL } from '@/lib/constants';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function LoginPage() {
|
||||
// Next.js router — programmatic navigation (router.push)
|
||||
const router = useRouter();
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { login, register } = useAuthStore();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [isRegister, setIsRegister] = useState(false);
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [error, setError] = useState('');
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [form, setForm] = useState({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
password: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: '',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 28: const handleSubmit = (e: React.FormEvent) => {
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
// Line 29: e.preventDefault();
|
||||
e.preventDefault();
|
||||
// Line 30: setError('');
|
||||
setError('');
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (isRegister) {
|
||||
// Line 33: const success = register({
|
||||
const success = register({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: form.name,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: form.email,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
password: form.password,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: form.phone,
|
||||
// Line 38: address: {
|
||||
address: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: form.street,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: form.city,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: form.state,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: form.zip,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (success) router.push('/account');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} else {
|
||||
// Line 47: const success = login(form.email, form.password);
|
||||
const success = login(form.email, form.password);
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (success) {
|
||||
// Line 49: router.push('/account');
|
||||
router.push('/account');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} else {
|
||||
// Line 51: setError(t('auth.invalidCredentials', { email: DEMO_EMAIL...
|
||||
setError(t('auth.invalidCredentials', { email: DEMO_EMAIL }));
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex min-h-[70vh] items-center justify-center bg-gray-50 px-4 py-12">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="w-full max-w-md">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-8 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-brand-700">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="font-display text-xl font-bold text-gold-400">
|
||||
// Line 62: {t('site.initials')}
|
||||
{t('site.initials')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="font-display text-2xl font-bold text-brand-900">
|
||||
// Line 66: {isRegister ? t('auth.createAccount') : t('auth.welcomeBa...
|
||||
{isRegister ? t('auth.createAccount') : t('auth.welcomeBack')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
// Line 69: {isRegister
|
||||
{isRegister
|
||||
// Line 70: ? t('auth.joinTagline', { name: t('site.name') })
|
||||
? t('auth.joinTagline', { name: t('site.name') })
|
||||
// Line 71: : t('auth.signInTagline', { name: t('site.name') })}
|
||||
: t('auth.signInTagline', { name: t('site.name') })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium p-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
// Line 77: {isRegister && (
|
||||
{isRegister && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.fullName')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 81: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 83: value={form.name}
|
||||
value={form.name}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
// Line 85: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 87: )}
|
||||
)}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.email')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 92: required
|
||||
required
|
||||
// Line 93: type="email"
|
||||
type="email"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 95: value={form.email}
|
||||
value={form.email}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
// Line 97: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.password')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 103: required
|
||||
required
|
||||
// Line 104: type="password"
|
||||
type="password"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 106: value={form.password}
|
||||
value={form.password}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||
// Line 108: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 111: {isRegister && (
|
||||
{isRegister && (
|
||||
// React Fragment — group elements without extra wrapper DOM node
|
||||
<>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.phone')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 116: required
|
||||
required
|
||||
// Line 117: type="tel"
|
||||
type="tel"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 119: value={form.phone}
|
||||
value={form.phone}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||
// Line 121: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.street')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 126: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 128: value={form.street}
|
||||
value={form.street}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, street: e.target.value })}
|
||||
// Line 130: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.city')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 136: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 138: value={form.city}
|
||||
value={form.city}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, city: e.target.value })}
|
||||
// Line 140: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.state')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 145: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 147: value={form.state}
|
||||
value={form.state}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, state: e.target.value })}
|
||||
// Line 149: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label className="label-text">{t('auth.zip')}</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 155: required
|
||||
required
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 157: value={form.zip}
|
||||
value={form.zip}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => setForm({ ...form, zip: e.target.value })}
|
||||
// Line 159: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</>
|
||||
// Line 162: )}
|
||||
)}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 164: {error && (
|
||||
{error && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="rounded-lg bg-red-50 px-4 py-2 text-sm text-red-600">{error}</p>
|
||||
// Line 166: )}
|
||||
)}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button type="submit" variant="primary" size="lg" className="w-full">
|
||||
// Line 169: {isRegister ? t('auth.register') : t('auth.signIn')}
|
||||
{isRegister ? t('auth.register') : t('auth.signIn')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</form>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mt-6 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => {
|
||||
// Line 176: setIsRegister(!isRegister);
|
||||
setIsRegister(!isRegister);
|
||||
// Line 177: setError('');
|
||||
setError('');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="text-sm font-medium text-brand-600 hover:text-brand-800"
|
||||
// Line 180: >
|
||||
>
|
||||
// Line 181: {isRegister ? t('auth.hasAccount') : t('auth.noAccount')}
|
||||
{isRegister ? t('auth.hasAccount') : t('auth.noAccount')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 185: {!isRegister && (
|
||||
{!isRegister && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mt-4 rounded-lg bg-brand-50 px-4 py-3 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-brand-700">
|
||||
// Line 188: {t('auth.demo', { email: DEMO_EMAIL })}
|
||||
{t('auth.demo', { email: DEMO_EMAIL })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 191: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 195: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/not-found.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function NotFound() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex min-h-[60vh] flex-col items-center justify-center px-4 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="mb-2 font-display text-6xl font-bold text-brand-900">
|
||||
// Line 13: {t('notFound.title')}
|
||||
{t('notFound.title')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-6 text-lg text-gray-500">{t('notFound.message')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button>{t('notFound.goHome')}</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 20: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Hero from '@/components/home/Hero';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import TrustBadges from '@/components/home/TrustBadges';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AboutPreview from '@/components/home/AboutPreview';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import CategoryGrid from '@/components/home/CategoryGrid';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import FeaturedProducts from '@/components/home/FeaturedProducts';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import HowItWorks from '@/components/home/HowItWorks';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import WeeklyOffers from '@/components/home/WeeklyOffers';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import SocialFollow from '@/components/home/SocialFollow';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import ContactPreview from '@/components/home/ContactPreview';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import CTA from '@/components/home/CTA';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function HomePage() {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// React Fragment — group elements without extra wrapper DOM node
|
||||
<>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Hero />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<TrustBadges />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AboutPreview />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<CategoryGrid />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<FeaturedProducts />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<HowItWorks />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<WeeklyOffers />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<CTA />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<SocialFollow />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ContactPreview />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</>
|
||||
// Line 26: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/product/[slug]/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useState } from 'react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Next.js App Router hooks — useRouter, useParams, useSearchParams
|
||||
import { notFound, useParams } from 'next/navigation';
|
||||
// Import external package or local module
|
||||
import {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
ShoppingCart,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
Heart,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
ChevronLeft,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
Minus,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
Plus,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
Check,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
ShieldCheck,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import CustomizationSelector from '@/components/product/CustomizationSelector';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { getProductBySlug } from '@/lib/products';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { getDefaultCustomization, getCustomizationLabel } from '@/lib/customization';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { formatPrice, getFormatLocale } from '@/lib/utils';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useCartStore } from '@/store/cart';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useWishlistStore } from '@/store/wishlist';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { ProductCustomization } from '@/types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function ProductPage() {
|
||||
// Read dynamic route segment ([slug] from URL)
|
||||
const params = useParams();
|
||||
// Line 29: const slug = params.slug as string;
|
||||
const slug = params.slug as string;
|
||||
// Line 30: const rawProduct = getProductBySlug(slug);
|
||||
const rawProduct = getProductBySlug(slug);
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t, locale } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [customization, setCustomization] = useState<ProductCustomization>(
|
||||
// Line 34: rawProduct ? getDefaultCustomization(rawProduct.category)...
|
||||
rawProduct ? getDefaultCustomization(rawProduct.category) : { type: 'fish' }
|
||||
// Line 35: );
|
||||
);
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [selectedImage, setSelectedImage] = useState(0);
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [added, setAdded] = useState(false);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const addItem = useCartStore((s) => s.addItem);
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { isInWishlist, toggleItem } = useWishlistStore();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (!rawProduct) {
|
||||
// Line 44: notFound();
|
||||
notFound();
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 47: const product = localizeProduct(rawProduct, t);
|
||||
const product = localizeProduct(rawProduct, t);
|
||||
// Line 48: const inWishlist = isInWishlist(product.id);
|
||||
const inWishlist = isInWishlist(product.id);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 50: const handleAddToCart = () => {
|
||||
const handleAddToCart = () => {
|
||||
// Line 51: const label = getCustomizationLabel(customization, t);
|
||||
const label = getCustomizationLabel(customization, t);
|
||||
// Line 52: addItem(rawProduct, customization, label, quantity);
|
||||
addItem(rawProduct, customization, label, quantity);
|
||||
// Line 53: setAdded(true);
|
||||
setAdded(true);
|
||||
// Line 54: setTimeout(() => setAdded(false), 2000);
|
||||
setTimeout(() => setAdded(false), 2000);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="bg-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/shop"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="mb-6 inline-flex items-center gap-1 text-sm font-medium text-gray-500 transition-colors hover:text-brand-700"
|
||||
// Line 63: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ChevronLeft className="h-4 w-4 rtl:rotate-180" />
|
||||
// Line 65: {t('product.backToShop')}
|
||||
{t('product.backToShop')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-10 lg:grid-cols-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative mb-4 aspect-square overflow-hidden rounded-2xl bg-gray-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 72: src={product.images[selectedImage] || product.image}
|
||||
src={product.images[selectedImage] || product.image}
|
||||
// Line 73: alt={product.name}
|
||||
alt={product.name}
|
||||
// Line 74: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover"
|
||||
// Line 76: priority
|
||||
priority
|
||||
// Line 77: sizes="(max-width: 1024px) 100vw, 640px"
|
||||
sizes="(max-width: 1024px) 100vw, 640px"
|
||||
// Line 78: />
|
||||
/>
|
||||
// Line 79: {product.badge && (
|
||||
{product.badge && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="absolute start-4 top-4 rounded-full bg-gold-500 px-4 py-1.5 text-sm font-semibold text-white shadow-gold">
|
||||
// Line 81: {product.badge}
|
||||
{product.badge}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 83: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 85: {product.images.length > 1 && (
|
||||
{product.images.length > 1 && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex gap-3">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{product.images.map((img, i) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 89: key={img}
|
||||
key={img}
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setSelectedImage(i)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={`relative h-20 w-20 overflow-hidden rounded-lg border-2 transition-all ${
|
||||
// Line 92: selectedImage === i
|
||||
selectedImage === i
|
||||
// Line 93: ? 'border-brand-700'
|
||||
? 'border-brand-700'
|
||||
// Line 94: : 'border-transparent opacity-60 hover:opacity-100'
|
||||
: 'border-transparent opacity-60 hover:opacity-100'
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}`}
|
||||
// Line 96: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage src={img} alt="" fill className="object-cover" sizes="80px" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 99: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 101: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={`/shop?category=${product.category}`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-full bg-brand-50 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-brand-700 transition-colors hover:bg-brand-100"
|
||||
// Line 109: >
|
||||
>
|
||||
// Line 110: {t(`categories.${product.category}.name`)}
|
||||
{t(`categories.${product.category}.name`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// Line 112: {product.inStock ? (
|
||||
{product.inStock ? (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="flex items-center gap-1 text-xs font-medium text-brand-700">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Check className="h-3 w-3" /> {t('product.inStock')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 116: ) : (
|
||||
) : (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-xs font-medium text-red-500">
|
||||
// Line 118: {t('product.outOfStock')}
|
||||
{t('product.outOfStock')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 120: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="mb-2 font-display text-3xl font-bold text-brand-900 lg:text-4xl">
|
||||
// Line 124: {product.name}
|
||||
{product.name}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h1>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-4 text-gray-500">{product.description}</p>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 flex items-baseline gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-3xl font-bold text-brand-800">
|
||||
// Line 131: {formatPrice(product.price, getFormatLocale(locale))}
|
||||
{formatPrice(product.price, getFormatLocale(locale))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-sm text-gray-400">{product.priceUnit}</span>
|
||||
// Line 134: {product.weight && (
|
||||
{product.weight && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-sm text-gray-400">· {product.weight}</span>
|
||||
// Line 136: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 flex items-center gap-2 rounded-lg border border-brand-100 bg-brand-50/50 px-4 py-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShieldCheck className="h-5 w-5 shrink-0 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-sm text-brand-800">{t('product.halalTrust')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 rounded-2xl border border-gray-100 bg-gray-50 p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<CustomizationSelector
|
||||
// Line 146: category={product.category}
|
||||
category={product.category}
|
||||
// Line 147: customization={customization}
|
||||
customization={customization}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={setCustomization}
|
||||
// Line 149: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mt-4 rounded-lg bg-white px-4 py-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-gray-500">{t('product.yourSelection')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm font-semibold text-brand-800">
|
||||
// Line 153: {getCustomizationLabel(customization, t)}
|
||||
{getCustomizationLabel(customization, t)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 flex items-center gap-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center rounded-lg border border-gray-200">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setQuantity(Math.max(1, quantity - 1))}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex h-12 w-12 items-center justify-center text-gray-500 transition-colors hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('product.decreaseQty')}
|
||||
// Line 164: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Minus className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="w-12 text-center font-semibold">{quantity}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setQuantity(quantity + 1)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex h-12 w-12 items-center justify-center text-gray-500 transition-colors hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('product.increaseQty')}
|
||||
// Line 172: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Plus className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={handleAddToCart}
|
||||
// Line 179: disabled={!product.inStock}
|
||||
disabled={!product.inStock}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex-1"
|
||||
// Line 181: size="lg"
|
||||
size="lg"
|
||||
// Line 182: >
|
||||
>
|
||||
// Line 183: {added ? (
|
||||
{added ? (
|
||||
// React Fragment — group elements without extra wrapper DOM node
|
||||
<>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Check className="h-4 w-4" />
|
||||
// Line 186: {t('product.addedToCart')}
|
||||
{t('product.addedToCart')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</>
|
||||
// Line 188: ) : (
|
||||
) : (
|
||||
// React Fragment — group elements without extra wrapper DOM node
|
||||
<>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShoppingCart className="h-4 w-4" />
|
||||
// Line 191: {t('product.addToCart')}
|
||||
{t('product.addToCart')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</>
|
||||
// Line 193: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => toggleItem(rawProduct)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={`flex h-12 w-12 items-center justify-center rounded-lg border-2 transition-all ${
|
||||
// Line 199: inWishlist
|
||||
inWishlist
|
||||
// Line 200: ? 'border-red-200 bg-red-50 text-red-500'
|
||||
? 'border-red-200 bg-red-50 text-red-500'
|
||||
// Line 201: : 'border-gray-200 text-gray-400 hover:border-brand-300 h...
|
||||
: 'border-gray-200 text-gray-400 hover:border-brand-300 hover:text-brand-700'
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}`}
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={
|
||||
// Line 204: inWishlist ? t('product.removeWishlist') : t('product.add...
|
||||
inWishlist ? t('product.removeWishlist') : t('product.addWishlist')
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Line 206: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Heart className={`h-5 w-5 ${inWishlist ? 'fill-current' : ''}`} />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="border-t border-gray-100 pt-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="mb-3 font-display text-lg font-semibold text-brand-900">
|
||||
// Line 213: {t('product.aboutProduct')}
|
||||
{t('product.aboutProduct')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm leading-relaxed text-gray-600">
|
||||
// Line 216: {product.longDescription}
|
||||
{product.longDescription}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 223: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/shop/layout.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { Suspense } from 'react';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const metadata = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Shop',
|
||||
// Line 5: description:
|
||||
description:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Browse premium halal chicken, beef, lamb, and fish. Customized cuts delivered fresh to your door.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function ShopLayout({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
children,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}: {
|
||||
// Line 12: children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}) {
|
||||
// Return value from function
|
||||
return <Suspense fallback={<ShopLoading />}>{children}</Suspense>;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Function declaration — reusable logic in this file
|
||||
function ShopLoading() {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex min-h-[50vh] items-center justify-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-brand-200 border-t-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 22: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/shop/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// Next.js App Router hooks — useRouter, useParams, useSearchParams
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import ProductCard from '@/components/product/ProductCard';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import ShopFilters from '@/components/shop/ShopFilters';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { products } from '@/lib/products';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Category, SortOption } from '@/types';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 12: const CATEGORIES: Category[] = ['chicken', 'beef', 'lamb'...
|
||||
const CATEGORIES: Category[] = ['chicken', 'beef', 'lamb', 'fish'];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Function declaration — reusable logic in this file
|
||||
function parseCategory(value: string | null): Category | 'all' {
|
||||
// Return value from function
|
||||
return CATEGORIES.includes(value as Category) ? (value as Category) : 'all';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function ShopPage() {
|
||||
// Next.js router — programmatic navigation (router.push)
|
||||
const router = useRouter();
|
||||
// Read URL query string (?category=beef&q=steak)
|
||||
const searchParams = useSearchParams();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [selectedCategory, setSelectedCategory] = useState<Category | 'all'>(
|
||||
// Line 24: parseCategory(searchParams.get('category'))
|
||||
parseCategory(searchParams.get('category'))
|
||||
// Line 25: );
|
||||
);
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [sortBy, setSortBy] = useState<SortOption>('featured');
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Side effect hook — runs after paint; deps array controls when it re-runs
|
||||
useEffect(() => {
|
||||
// Line 30: setSelectedCategory(parseCategory(searchParams.get('categ...
|
||||
setSelectedCategory(parseCategory(searchParams.get('category')));
|
||||
// Line 31: setSearchQuery(searchParams.get('q') || '');
|
||||
setSearchQuery(searchParams.get('q') || '');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}, [searchParams]);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// React useCallback — stable function reference for useEffect/useMemo deps
|
||||
const pushParams = useCallback(
|
||||
// Line 35: (updates: { category?: Category | 'all'; q?: string }) => {
|
||||
(updates: { category?: Category | 'all'; q?: string }) => {
|
||||
// Line 36: const params = new URLSearchParams(searchParams.toString());
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (updates.category !== undefined) {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (updates.category === 'all') params.delete('category');
|
||||
// Line 40: else params.set('category', updates.category);
|
||||
else params.set('category', updates.category);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (updates.q !== undefined) {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (updates.q) params.set('q', updates.q);
|
||||
// Line 45: else params.delete('q');
|
||||
else params.delete('q');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 48: const qs = params.toString();
|
||||
const qs = params.toString();
|
||||
// Line 49: router.push(qs ? `/shop?${qs}` : '/shop', { scroll: false...
|
||||
router.push(qs ? `/shop?${qs}` : '/shop', { scroll: false });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 51: [router, searchParams]
|
||||
[router, searchParams]
|
||||
// Line 52: );
|
||||
);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 54: const handleCategoryChange = (category: Category | 'all')...
|
||||
const handleCategoryChange = (category: Category | 'all') => {
|
||||
// Line 55: setSelectedCategory(category);
|
||||
setSelectedCategory(category);
|
||||
// Line 56: pushParams({ category });
|
||||
pushParams({ category });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 59: const handleSearchChange = (query: string) => {
|
||||
const handleSearchChange = (query: string) => {
|
||||
// Line 60: setSearchQuery(query);
|
||||
setSearchQuery(query);
|
||||
// Line 61: pushParams({ q: query });
|
||||
pushParams({ q: query });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// React useMemo — cache expensive computed value until dependencies change
|
||||
const filteredProducts = useMemo(() => {
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
let result = products.map((p) => localizeProduct(p, t));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (selectedCategory !== 'all') {
|
||||
// Array.filter — keep items matching condition (search, category)
|
||||
result = result.filter((p) => p.category === selectedCategory);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (searchQuery.trim()) {
|
||||
// Line 72: const query = searchQuery.toLowerCase();
|
||||
const query = searchQuery.toLowerCase();
|
||||
// Array.filter — keep items matching condition (search, category)
|
||||
result = result.filter(
|
||||
// Line 74: (p) =>
|
||||
(p) =>
|
||||
// Line 75: p.name.toLowerCase().includes(query) ||
|
||||
p.name.toLowerCase().includes(query) ||
|
||||
// Line 76: p.description.toLowerCase().includes(query) ||
|
||||
p.description.toLowerCase().includes(query) ||
|
||||
// Line 77: p.tags.some((tag) => tag.includes(query))
|
||||
p.tags.some((tag) => tag.includes(query))
|
||||
// Line 78: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Switch — multiple branches on one variable (e.g. sort order)
|
||||
switch (sortBy) {
|
||||
// Switch case — handle one specific value
|
||||
case 'price-asc':
|
||||
// Array.sort — reorder items (price, name, featured)
|
||||
result.sort((a, b) => a.price - b.price);
|
||||
// Line 84: break;
|
||||
break;
|
||||
// Switch case — handle one specific value
|
||||
case 'price-desc':
|
||||
// Array.sort — reorder items (price, name, featured)
|
||||
result.sort((a, b) => b.price - a.price);
|
||||
// Line 87: break;
|
||||
break;
|
||||
// Switch case — handle one specific value
|
||||
case 'name':
|
||||
// Array.sort — reorder items (price, name, featured)
|
||||
result.sort((a, b) => a.name.localeCompare(b.name));
|
||||
// Line 90: break;
|
||||
break;
|
||||
// Switch case — handle one specific value
|
||||
case 'featured':
|
||||
// Switch default — fallback when no case matches
|
||||
default:
|
||||
// Array.sort — reorder items (price, name, featured)
|
||||
result.sort((a, b) => (b.featured ? 1 : 0) - (a.featured ? 1 : 0));
|
||||
// Line 94: break;
|
||||
break;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return result;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}, [selectedCategory, sortBy, searchQuery, t]);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="bg-gray-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="border-b border-gray-100 bg-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="section-heading mb-2">{t('shop.title')}</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-gray-500">{t('shop.subtitle')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-col gap-8 lg:flex-row">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<aside className="lg:w-64 lg:shrink-0">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium sticky top-24 p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShopFilters
|
||||
// Line 114: selectedCategory={selectedCategory}
|
||||
selectedCategory={selectedCategory}
|
||||
// Line 115: onCategoryChange={handleCategoryChange}
|
||||
onCategoryChange={handleCategoryChange}
|
||||
// Line 116: sortBy={sortBy}
|
||||
sortBy={sortBy}
|
||||
// Line 117: onSortChange={setSortBy}
|
||||
onSortChange={setSortBy}
|
||||
// Line 118: searchQuery={searchQuery}
|
||||
searchQuery={searchQuery}
|
||||
// Line 119: onSearchChange={handleSearchChange}
|
||||
onSearchChange={handleSearchChange}
|
||||
// Line 120: totalResults={filteredProducts.length}
|
||||
totalResults={filteredProducts.length}
|
||||
// Line 121: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</aside>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex-1">
|
||||
// Line 126: {filteredProducts.length === 0 ? (
|
||||
{filteredProducts.length === 0 ? (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium flex flex-col items-center justify-center p-16 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-2 text-lg font-semibold text-brand-900">
|
||||
// Line 129: {t('shop.noProducts')}
|
||||
{t('shop.noProducts')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm text-gray-500">{t('shop.noProductsHint')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 133: ) : (
|
||||
) : (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{filteredProducts.map((product) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ProductCard key={product.id} product={product} />
|
||||
// Line 137: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 139: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 144: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/app/wishlist/page.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Heart } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import ProductCard from '@/components/product/ProductCard';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useWishlistStore } from '@/store/wishlist';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function WishlistPage() {
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const items = useWishlistStore((s) => s.items);
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
const localizedItems = items.map((p) => localizeProduct(p, t));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="bg-gray-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="border-b border-gray-100 bg-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="section-heading">{t('wishlist.title')}</h1>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-gray-500">
|
||||
// Line 22: {t('wishlist.saved', { count: items.length })}
|
||||
{t('wishlist.saved', { count: items.length })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
// Line 28: {items.length === 0 ? (
|
||||
{items.length === 0 ? (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium flex flex-col items-center justify-center p-16 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-red-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Heart className="h-8 w-8 text-red-300" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="mb-2 text-lg font-semibold text-brand-900">
|
||||
// Line 34: {t('wishlist.empty')}
|
||||
{t('wishlist.empty')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-6 text-sm text-gray-500">{t('wishlist.emptyHint')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button>{t('wishlist.browse')}</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 41: ) : (
|
||||
) : (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{localizedItems.map((product) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ProductCard key={product.id} product={product} />
|
||||
// Line 45: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 47: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 50: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/AboutPreview.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { IMAGES } from '@/lib/images';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function AboutPreview() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 13: const stats = [
|
||||
const stats = [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: t('aboutPreview.statHalal'), label: t('aboutPreview.statHalalLabel') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: t('aboutPreview.statDays'), label: t('aboutPreview.statDaysLabel') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: t('aboutPreview.statDelivery'), label: t('aboutPreview.statDeliveryLabel') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: t('aboutPreview.statFresh'), label: t('aboutPreview.statFreshLabel') },
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="bg-white py-20">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid items-center gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="mb-2 inline-block text-sm font-semibold uppercase tracking-wider text-gold-600">
|
||||
// Line 26: {t('aboutPreview.label')}
|
||||
{t('aboutPreview.label')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-6">{t('aboutPreview.title')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-4 leading-relaxed text-gray-600">{t('aboutPreview.p1')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-4 leading-relaxed text-gray-600">{t('aboutPreview.p2')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-8 leading-relaxed text-gray-600">{t('aboutPreview.p3')}</p>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-8 grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{stats.map((stat) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div
|
||||
// Line 36: key={stat.label}
|
||||
key={stat.label}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-xl border border-cream-300/80 bg-cream-50 p-4 text-center"
|
||||
// Line 38: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="font-display text-2xl font-bold text-brand-800">{stat.value}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-1 text-xs font-medium text-gray-500">{stat.label}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 42: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/about">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="secondary">
|
||||
// Line 47: {t('aboutPreview.readMore')}
|
||||
{t('aboutPreview.readMore')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative aspect-[4/5] overflow-hidden rounded-2xl shadow-premium-lg">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 55: src={IMAGES.aboutMeat}
|
||||
src={IMAGES.aboutMeat}
|
||||
// Line 56: alt={t('aboutPreview.imageAlt')}
|
||||
alt={t('aboutPreview.imageAlt')}
|
||||
// Line 57: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover"
|
||||
// Line 59: sizes="(max-width: 1024px) 100vw, 640px"
|
||||
sizes="(max-width: 1024px) 100vw, 640px"
|
||||
// Line 60: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-brand-950/30 via-transparent to-transparent" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 66: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/CTA.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ArrowRight, MessageCircle } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { WHATSAPP_URL } from '@/lib/constants';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function CTA() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="py-20">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-brand-900 px-8 py-16 text-center sm:px-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute -right-20 -top-20 h-60 w-60 rounded-full bg-gold-500/10" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute -bottom-16 -left-16 h-48 w-48 rounded-full bg-gold-500/10" />
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="mb-4 font-display text-3xl font-bold text-white md:text-4xl">
|
||||
// Line 21: {t('cta.title')}
|
||||
{t('cta.title')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mx-auto mb-8 max-w-xl text-brand-100">{t('cta.subtitle')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="gold" size="lg">
|
||||
// Line 27: {t('cta.button')}
|
||||
{t('cta.button')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button
|
||||
// Line 33: variant="secondary"
|
||||
variant="secondary"
|
||||
// Line 34: size="lg"
|
||||
size="lg"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15"
|
||||
// Line 36: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
// Line 38: {t('cta.whatsapp')}
|
||||
{t('cta.whatsapp')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 46: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/CategoryGrid.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { CATEGORY_IDS, CATEGORY_IMAGES } from '@/lib/constants';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function CategoryGrid() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="bg-cream-50 py-20">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-12 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-3">{t('categories.title')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mx-auto max-w-2xl text-gray-500">{t('categories.subtitle')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{CATEGORY_IDS.map((id) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Line 22: key={id}
|
||||
key={id}
|
||||
// Link target URL — internal route or external https://
|
||||
href={`/shop?category=${id}`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="group relative overflow-hidden rounded-2xl shadow-premium transition-all duration-300 hover:shadow-premium-lg"
|
||||
// Line 25: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative aspect-[3/4]">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 28: src={CATEGORY_IMAGES[id]}
|
||||
src={CATEGORY_IMAGES[id]}
|
||||
// Line 29: alt={t(`categories.${id}.name`)}
|
||||
alt={t(`categories.${id}.name`)}
|
||||
// Line 30: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
// Line 32: sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw...
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 320px"
|
||||
// Line 33: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-brand-950/92 via-brand-800/50 to-brand-700/10" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-1 font-display text-2xl font-bold text-white">
|
||||
// Line 38: {t(`categories.${id}.name`)}
|
||||
{t(`categories.${id}.name`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-3 text-sm text-brand-200">
|
||||
// Line 41: {t(`categories.${id}.description`)}
|
||||
{t(`categories.${id}.description`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="inline-flex items-center gap-1 text-sm font-semibold text-gold-400 transition-colors group-hover:text-gold-300">
|
||||
// Line 44: {t('categories.shop', { name: t(`categories.${id}.name`) })}
|
||||
{t('categories.shop', { name: t(`categories.${id}.name`) })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1 rtl:rotate-180 rtl:group-hover:-translate-x-1" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// Line 49: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 53: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/ContactPreview.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Phone, MapPin, Clock, MessageCircle, ExternalLink } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Logo from '@/components/ui/Logo';
|
||||
// Import external package or local module
|
||||
import {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_ADDRESS,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_PHONE_DISPLAY,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_HOURS,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
WHATSAPP_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
MAPS_URL,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} from '@/lib/constants';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function ContactPreview() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// Line 18: const telHref = `tel:+46725855050`;
|
||||
const telHref = `tel:+46725855050`;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="bg-brand-950 py-20 text-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="mb-2 inline-block text-sm font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 26: {t('contact.label')}
|
||||
{t('contact.label')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="mb-8 font-display text-3xl font-bold md:text-4xl">
|
||||
// Line 29: {t('contact.title')}
|
||||
{t('contact.title')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ul className="mb-8 space-y-5">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li className="flex gap-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MapPin className="mt-0.5 h-5 w-5 shrink-0 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 37: {t('contact.addressLabel')}
|
||||
{t('contact.addressLabel')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-brand-100">{SITE_ADDRESS}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li className="flex gap-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Phone className="mt-0.5 h-5 w-5 shrink-0 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 46: {t('contact.phoneLabel')}
|
||||
{t('contact.phoneLabel')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={telHref} className="text-brand-100 hover:text-white">
|
||||
// Line 49: {SITE_PHONE_DISPLAY}
|
||||
{SITE_PHONE_DISPLAY}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li className="flex gap-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Clock className="mt-0.5 h-5 w-5 shrink-0 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 57: {t('contact.hoursLabel')}
|
||||
{t('contact.hoursLabel')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-brand-100">
|
||||
// Line 60: {t('contact.hoursValue', { hours: SITE_HOURS })}
|
||||
{t('contact.hoursValue', { hours: SITE_HOURS })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</ul>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/about#contact">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button
|
||||
// Line 69: variant="secondary"
|
||||
variant="secondary"
|
||||
// Line 70: size="lg"
|
||||
size="lg"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="w-full border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15 sm:w-auto"
|
||||
// Line 72: >
|
||||
>
|
||||
// Line 73: {t('contact.learnMore')}
|
||||
{t('contact.learnMore')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="gold" size="lg" className="w-full sm:w-auto">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
// Line 79: {t('contact.writeUs')}
|
||||
{t('contact.writeUs')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={telHref}>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button
|
||||
// Line 84: variant="secondary"
|
||||
variant="secondary"
|
||||
// Line 85: size="lg"
|
||||
size="lg"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="w-full border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15 sm:w-auto"
|
||||
// Line 87: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Phone className="h-4 w-4" />
|
||||
// Line 89: {t('contact.callUs')}
|
||||
{t('contact.callUs')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-col justify-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a
|
||||
// Link target URL — internal route or external https://
|
||||
href={MAPS_URL}
|
||||
// Line 98: target="_blank"
|
||||
target="_blank"
|
||||
// Line 99: rel="noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="card-premium group flex flex-col items-center gap-4 bg-brand-900/50 p-8 text-center transition-all hover:bg-brand-900/70"
|
||||
// Line 101: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Logo size="lg" className="ring-gold-400/30" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="font-display text-xl font-bold">{t('site.name')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-1 text-sm text-brand-200">{SITE_ADDRESS}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="inline-flex items-center gap-2 text-sm font-semibold text-gold-400 transition-colors group-hover:text-gold-300">
|
||||
// Line 108: {t('contact.openMaps')}
|
||||
{t('contact.openMaps')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 116: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/FeaturedProducts.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import ProductCard from '@/components/product/ProductCard';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { getFeaturedProducts } from '@/lib/products';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function FeaturedProducts() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// Line 13: const featured = getFeaturedProducts()
|
||||
const featured = getFeaturedProducts()
|
||||
// Line 14: .slice(0, 4)
|
||||
.slice(0, 4)
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
.map((p) => localizeProduct(p, t));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="py-20">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-12 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-end">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="mb-2 inline-block text-sm font-semibold uppercase tracking-wider text-gold-600">
|
||||
// Line 23: {t('featured.label')}
|
||||
{t('featured.label')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading">{t('featured.title')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-2 max-w-lg text-gray-500">{t('featured.subtitle')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="secondary">
|
||||
// Line 30: {t('featured.viewAll')}
|
||||
{t('featured.viewAll')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{featured.map((product) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ProductCard key={product.id} product={product} />
|
||||
// Line 39: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 43: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/Hero.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Logo from '@/components/ui/Logo';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ArrowRight, ShieldCheck, MapPin, Clock } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { IMAGES } from '@/lib/images';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { SITE_LOCATION, SITE_HOURS, SITE_ADDRESS, WHATSAPP_URL } from '@/lib/constants';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function Hero() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="relative min-h-[85vh] overflow-hidden bg-brand-950">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute inset-0">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 19: src={IMAGES.hero}
|
||||
src={IMAGES.hero}
|
||||
// Line 20: alt=""
|
||||
alt=""
|
||||
// Line 21: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover"
|
||||
// Line 23: priority
|
||||
priority
|
||||
// Line 24: sizes="100vw"
|
||||
sizes="100vw"
|
||||
// Line 25: placeholder="blur"
|
||||
placeholder="blur"
|
||||
// Line 26: blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAA...
|
||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAUH/8QAIhAAAgEDBQAAAAAAAAAAAAAAAQIDAAQRBQYhIjFB/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAZEQACAwEAAAAAAAAAAAAAAAAAAQIRITH/2gAMAwEAAhEDEEA/ALextba0t0t7eJI4kHVEU4AqT/9k="
|
||||
// Line 27: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-brand-950/95 via-brand-900/85 to-brand-900/40" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-brand-950/60 via-transparent to-transparent" />
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative mx-auto flex min-h-[85vh] max-w-7xl flex-col justify-center px-4 py-20 sm:px-6 lg:px-8 lg:py-28">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="max-w-2xl animate-slide-up">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-8 flex items-center gap-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Logo size="lg" className="ring-gold-400/40 shadow-premium-lg" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm font-medium text-gold-400">{SITE_LOCATION}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-brand-200">{t('hero.taglineShort')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-gold-500/30 bg-brand-800/50 px-4 py-1.5 backdrop-blur-sm">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShieldCheck className="h-4 w-4 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-gold-300">
|
||||
// Line 45: {t('hero.badge')}
|
||||
{t('hero.badge')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h1 className="mb-4 font-display text-4xl font-bold leading-tight text-white sm:text-5xl lg:text-6xl">
|
||||
// Line 50: {t('hero.title')}
|
||||
{t('hero.title')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h1>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-2 text-xl font-medium text-gold-300 sm:text-2xl">
|
||||
// Line 54: {t('hero.subtitleShort')}
|
||||
{t('hero.subtitleShort')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-8 text-base leading-relaxed text-brand-100 sm:text-lg">
|
||||
// Line 58: {t('hero.subtitle')}
|
||||
{t('hero.subtitle')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-8 flex flex-wrap gap-4 text-sm text-brand-200">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="flex items-center gap-1.5">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Clock className="h-4 w-4 text-gold-400" />
|
||||
// Line 64: {t('hero.hours', { hours: SITE_HOURS })}
|
||||
{t('hero.hours', { hours: SITE_HOURS })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="flex items-center gap-1.5">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MapPin className="h-4 w-4 text-gold-400" />
|
||||
// Line 68: {SITE_ADDRESS}
|
||||
{SITE_ADDRESS}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-col gap-4 sm:flex-row">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/shop">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="gold" size="lg">
|
||||
// Line 75: {t('hero.shopNow')}
|
||||
{t('hero.shopNow')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button
|
||||
// Line 81: variant="secondary"
|
||||
variant="secondary"
|
||||
// Line 82: size="lg"
|
||||
size="lg"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15"
|
||||
// Line 84: >
|
||||
>
|
||||
// Line 85: {t('hero.whatsapp')}
|
||||
{t('hero.whatsapp')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-cream-50 to-transparent" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 94: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/HowItWorks.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { MessageCircle, ClipboardCheck, Store } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Button from '@/components/ui/Button';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { WHATSAPP_URL } from '@/lib/constants';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function HowItWorks() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 11: const steps = [
|
||||
const steps = [
|
||||
// Line 12: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: MessageCircle,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step: '01',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('howItWorks.step1Title'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t('howItWorks.step1Desc'),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 18: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: ClipboardCheck,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step: '02',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('howItWorks.step2Title'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t('howItWorks.step2Desc'),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 24: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: Store,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step: '03',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('howItWorks.step3Title'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t('howItWorks.step3Desc'),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="bg-brand-950 py-20 text-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-12 text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="mb-2 inline-block text-sm font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 37: {t('howItWorks.label')}
|
||||
{t('howItWorks.label')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="font-display text-3xl font-bold md:text-4xl">
|
||||
// Line 40: {t('howItWorks.title')}
|
||||
{t('howItWorks.title')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-12 grid gap-8 md:grid-cols-3">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{steps.map((step) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div key={step.step} className="relative text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-brand-800">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<step.icon className="h-7 w-7 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="mb-2 inline-block text-xs font-bold uppercase tracking-widest text-gold-500">
|
||||
// Line 51: {t('howItWorks.step', { n: step.step })}
|
||||
{t('howItWorks.step', { n: step.step })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-2 font-display text-xl font-bold">{step.title}</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm leading-relaxed text-brand-200">{step.description}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 56: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="text-center">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Button variant="gold" size="lg">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
// Line 63: {t('howItWorks.whatsapp')}
|
||||
{t('howItWorks.whatsapp')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 69: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/SocialFollow.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Facebook, Instagram, MessageCircle } from 'lucide-react';
|
||||
// Import external package or local module
|
||||
import {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
FACEBOOK_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
INSTAGRAM_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
WHATSAPP_URL,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} from '@/lib/constants';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 11: const socialLinks = [
|
||||
const socialLinks = [
|
||||
// Line 12: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
key: 'facebook',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
href: FACEBOOK_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: Facebook,
|
||||
// Line 16: className:
|
||||
className:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'border-blue-600/20 bg-blue-600/10 text-blue-700 hover:border-blue-600/40 hover:bg-blue-600/15',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 19: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
key: 'instagram',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
href: INSTAGRAM_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: Instagram,
|
||||
// Line 23: className:
|
||||
className:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'border-pink-600/20 bg-pink-600/10 text-pink-700 hover:border-pink-600/40 hover:bg-pink-600/15',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 26: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
key: 'whatsapp',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
href: WHATSAPP_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: MessageCircle,
|
||||
// Line 30: className:
|
||||
className:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'border-green-600/20 bg-green-600/10 text-green-700 hover:border-green-600/40 hover:bg-green-600/15',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// End of array literal
|
||||
] as const;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function SocialFollow() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="py-20">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="rounded-3xl border border-cream-300/80 bg-white px-8 py-12 text-center shadow-premium sm:px-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="mb-2 inline-block text-sm font-semibold uppercase tracking-wider text-gold-600">
|
||||
// Line 43: {t('social.label')}
|
||||
{t('social.label')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-3">{t('social.title')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mx-auto mb-8 max-w-xl text-gray-500">{t('social.subtitle')}</p>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{socialLinks.map(({ key, href, icon: Icon, className }) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a
|
||||
// Line 51: key={key}
|
||||
key={key}
|
||||
// Link target URL — internal route or external https://
|
||||
href={href}
|
||||
// Line 53: target="_blank"
|
||||
target="_blank"
|
||||
// Line 54: rel="noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={`inline-flex items-center gap-2 rounded-xl border-2 px-6 py-3 text-sm font-semibold transition-all ${className}`}
|
||||
// Line 56: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Icon className="h-5 w-5" />
|
||||
// Line 58: {t(`social.${key}`)}
|
||||
{t(`social.${key}`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// Line 60: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 65: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/TrustBadges.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ShieldCheck, Leaf, Award } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function TrustBadges() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 10: const badges = [
|
||||
const badges = [
|
||||
// Line 11: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: ShieldCheck,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('trust.halal'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t('trust.halalDesc'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
href: '/about#halal',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 17: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: Leaf,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('trust.fresh'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t('trust.freshDesc'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
href: '/about#delivery',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 23: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
icon: Award,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: t('trust.premium'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t('trust.premiumDesc'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
href: '/about',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="bg-cream-100 py-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{badges.map((badge) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Line 37: key={badge.title}
|
||||
key={badge.title}
|
||||
// Link target URL — internal route or external https://
|
||||
href={badge.href}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="group rounded-2xl border border-cream-300/80 bg-white p-8 text-center shadow-premium transition-all duration-300 hover:border-brand-200 hover:shadow-premium-lg"
|
||||
// Line 40: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-brand-50 transition-colors group-hover:bg-gold-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<badge.icon className="h-7 w-7 text-brand-700 transition-colors group-hover:text-gold-600" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-2 font-display text-xl font-bold text-brand-900">
|
||||
// Line 45: {badge.title}
|
||||
{badge.title}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm leading-relaxed text-gray-500">{badge.description}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// Line 49: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 53: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/home/WeeklyOffers.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { MessageCircle, ArrowRight } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { weeklyOffers } from '@/lib/offers';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { IMAGES } from '@/lib/images';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { whatsappOrderUrl } from '@/lib/constants';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { formatPrice, getFormatLocale } from '@/lib/utils';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function WeeklyOffers() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t, locale } = useTranslation();
|
||||
// Line 14: const fmt = (n: number) => formatPrice(n, getFormatLocale...
|
||||
const fmt = (n: number) => formatPrice(n, getFormatLocale(locale));
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<section className="bg-cream-100 py-20">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid items-center gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="mb-2 inline-block text-sm font-semibold uppercase tracking-wider text-gold-600">
|
||||
// Line 22: {t('offers.label')}
|
||||
{t('offers.label')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="section-heading mb-3">{t('offers.title')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-8 text-gray-500">{t('offers.subtitle')}</p>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-4">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{weeklyOffers.map((offer) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div
|
||||
// Line 30: key={offer.id}
|
||||
key={offer.id}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="group flex gap-4 rounded-2xl border border-cream-300/80 bg-white p-4 shadow-premium transition-all duration-300 hover:border-brand-200 hover:shadow-premium-lg sm:p-5"
|
||||
// Line 32: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={`/product/${offer.productSlug}`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="relative h-20 w-20 shrink-0 overflow-hidden rounded-xl sm:h-24 sm:w-24"
|
||||
// Line 36: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 38: src={offer.image}
|
||||
src={offer.image}
|
||||
// Line 39: alt={t(`${offer.nameKey}.name`)}
|
||||
alt={t(`${offer.nameKey}.name`)}
|
||||
// Line 40: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
// Line 42: sizes="(max-width: 640px) 96px, 128px"
|
||||
sizes="(max-width: 640px) 96px, 128px"
|
||||
// Line 43: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="min-w-0 flex-1">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-1 flex flex-wrap items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={
|
||||
// Line 50: offer.badgeKey === 'halal'
|
||||
offer.badgeKey === 'halal'
|
||||
// Line 51: ? 'rounded-full bg-brand-700 px-2.5 py-0.5 text-[10px] fo...
|
||||
? 'rounded-full bg-brand-700 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-white'
|
||||
// Line 52: : 'rounded-full bg-gold-500 px-2.5 py-0.5 text-[10px] fon...
|
||||
: 'rounded-full bg-gold-500 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-white'
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Line 54: >
|
||||
>
|
||||
// Line 55: {t(`offers.badge.${offer.badgeKey}`)}
|
||||
{t(`offers.badge.${offer.badgeKey}`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href={`/product/${offer.productSlug}`}>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-2 font-display text-lg font-bold text-brand-900 transition-colors hover:text-brand-700">
|
||||
// Line 61: {t(`${offer.nameKey}.name`)}
|
||||
{t(`${offer.nameKey}.name`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-wrap items-end justify-between gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// Line 67: {offer.originalPrice != null && (
|
||||
{offer.originalPrice != null && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm text-gray-400 line-through">
|
||||
// Line 69: {t('offers.was')}: {fmt(offer.originalPrice)}/
|
||||
{t('offers.was')}: {fmt(offer.originalPrice)}/
|
||||
// Line 70: {t(`priceUnit.${offer.priceUnitKey}`)}
|
||||
{t(`priceUnit.${offer.priceUnitKey}`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// Line 72: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="font-display text-xl font-bold text-brand-800">
|
||||
// Line 74: {offer.originalPrice != null && (
|
||||
{offer.originalPrice != null && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="me-2 text-sm font-semibold uppercase text-gold-600">
|
||||
// Line 76: {t('offers.now')}
|
||||
{t('offers.now')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 78: )}
|
||||
)}
|
||||
// Line 79: {fmt(offer.price)}
|
||||
{fmt(offer.price)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-sm font-normal text-gray-500">
|
||||
// Line 81: /{t(`priceUnit.${offer.priceUnitKey}`)}
|
||||
/{t(`priceUnit.${offer.priceUnitKey}`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-wrap gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={`/product/${offer.productSlug}`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="inline-flex items-center gap-1 rounded-lg border border-brand-200 px-3 py-2 text-xs font-semibold text-brand-700 transition-colors hover:bg-brand-50"
|
||||
// Line 90: >
|
||||
>
|
||||
// Line 91: {t('offers.viewProduct')}
|
||||
{t('offers.viewProduct')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ArrowRight className="h-3 w-3 rtl:rotate-180" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a
|
||||
// Link target URL — internal route or external https://
|
||||
href={whatsappOrderUrl(offer.whatsappProduct)}
|
||||
// Line 96: target="_blank"
|
||||
target="_blank"
|
||||
// Line 97: rel="noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-brand-700 px-4 py-2 text-xs font-semibold text-white transition-colors hover:bg-brand-800"
|
||||
// Line 99: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MessageCircle className="h-3.5 w-3.5" />
|
||||
// Line 101: {t('offers.order')}
|
||||
{t('offers.order')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 107: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-6 text-xs text-gray-400">{t('offers.disclaimer')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/shop"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="relative hidden aspect-[4/5] overflow-hidden rounded-2xl shadow-premium-lg lg:block"
|
||||
// Line 116: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 118: src={IMAGES.hero}
|
||||
src={IMAGES.hero}
|
||||
// Line 119: alt=""
|
||||
alt=""
|
||||
// Line 120: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover transition-transform duration-500 hover:scale-105"
|
||||
// Line 122: sizes="(max-width: 1024px) 0px, 640px"
|
||||
sizes="(max-width: 1024px) 0px, 640px"
|
||||
// Line 123: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-brand-950/50 via-transparent to-transparent" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</section>
|
||||
// Line 129: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/layout/Footer.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ShieldCheck, Leaf, Award, Phone, Mail, MapPin, Facebook, Instagram, MessageCircle } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Logo from '@/components/ui/Logo';
|
||||
// Import external package or local module
|
||||
import {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_PHONE_DISPLAY,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_ADDRESS,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_HOURS,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
SITE_EMAIL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
FACEBOOK_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
INSTAGRAM_URL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
WHATSAPP_URL,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} from '@/lib/constants';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function Footer() {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 20: const footerLinks = {
|
||||
const footerLinks = {
|
||||
// Line 21: shop: [
|
||||
shop: [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.chicken'), href: '/shop?category=chicken' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.beef'), href: '/shop?category=beef' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.lamb'), href: '/shop?category=lamb' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.fish'), href: '/shop?category=fish' },
|
||||
// End of array literal
|
||||
],
|
||||
// Line 27: company: [
|
||||
company: [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.about'), href: '/about' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.halalCert'), href: '/about#halal' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.delivery'), href: '/about#delivery' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.contact'), href: '/about#contact' },
|
||||
// End of array literal
|
||||
],
|
||||
// Line 33: account: [
|
||||
account: [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.myAccount'), href: '/account' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.orderHistory'), href: '/account#orders' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.wishlist'), href: '/wishlist' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ label: t('nav.cart'), href: '/cart' },
|
||||
// End of array literal
|
||||
],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<footer className="border-t border-gray-100 bg-brand-950 text-white">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid gap-12 md:grid-cols-2 lg:grid-cols-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/" className="mb-4 flex items-center gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Logo size="sm" className="ring-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="font-display text-xl font-bold">{t('site.name')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-6 text-sm leading-relaxed text-brand-200">
|
||||
// Line 51: {t('footer.tagline')}
|
||||
{t('footer.tagline')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-6 flex flex-wrap gap-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-1.5 text-xs text-gold-400">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShieldCheck className="h-4 w-4" />
|
||||
// Line 56: {t('trust.halal')}
|
||||
{t('trust.halal')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-1.5 text-xs text-gold-400">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Leaf className="h-4 w-4" />
|
||||
// Line 60: {t('trust.fresh')}
|
||||
{t('trust.fresh')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-1.5 text-xs text-gold-400">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Award className="h-4 w-4" />
|
||||
// Line 64: {t('trust.premium')}
|
||||
{t('trust.premium')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a
|
||||
// Link target URL — internal route or external https://
|
||||
href={FACEBOOK_URL}
|
||||
// Line 70: target="_blank"
|
||||
target="_blank"
|
||||
// Line 71: rel="noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('social.facebook')}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-lg bg-brand-800 p-2.5 text-brand-200 transition-colors hover:bg-brand-700 hover:text-white"
|
||||
// Line 74: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Facebook className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a
|
||||
// Link target URL — internal route or external https://
|
||||
href={INSTAGRAM_URL}
|
||||
// Line 79: target="_blank"
|
||||
target="_blank"
|
||||
// Line 80: rel="noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('social.instagram')}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-lg bg-brand-800 p-2.5 text-brand-200 transition-colors hover:bg-brand-700 hover:text-white"
|
||||
// Line 83: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Instagram className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a
|
||||
// Link target URL — internal route or external https://
|
||||
href={WHATSAPP_URL}
|
||||
// Line 88: target="_blank"
|
||||
target="_blank"
|
||||
// Line 89: rel="noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('social.whatsapp')}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-lg bg-brand-800 p-2.5 text-brand-200 transition-colors hover:bg-brand-700 hover:text-white"
|
||||
// Line 92: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 100: {t('footer.shop')}
|
||||
{t('footer.shop')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ul className="space-y-2">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{footerLinks.shop.map((link) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li key={link.href}>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={link.href}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="text-sm text-brand-200 transition-colors hover:text-white"
|
||||
// Line 108: >
|
||||
>
|
||||
// Line 109: {link.label}
|
||||
{link.label}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// Line 112: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</ul>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 118: {t('footer.company')}
|
||||
{t('footer.company')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ul className="space-y-2">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{footerLinks.company.map((link) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li key={link.href}>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={link.href}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="text-sm text-brand-200 transition-colors hover:text-white"
|
||||
// Line 126: >
|
||||
>
|
||||
// Line 127: {link.label}
|
||||
{link.label}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// Line 130: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</ul>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gold-400">
|
||||
// Line 136: {t('footer.contact')}
|
||||
{t('footer.contact')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ul className="space-y-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li className="flex items-center gap-2 text-sm text-brand-200">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Phone className="h-4 w-4 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={`tel:${SITE_PHONE_DISPLAY.replace(/\s/g, '')}`} className="hover:text-white">
|
||||
// Line 142: {SITE_PHONE_DISPLAY}
|
||||
{SITE_PHONE_DISPLAY}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li className="flex items-center gap-2 text-sm text-brand-200">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Mail className="h-4 w-4 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<a href={`mailto:${SITE_EMAIL}`} className="hover:text-white">
|
||||
// Line 148: {SITE_EMAIL}
|
||||
{SITE_EMAIL}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</a>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<li className="flex items-start gap-2 text-sm text-brand-200">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<MapPin className="mt-0.5 h-4 w-4 shrink-0 text-gold-400" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>
|
||||
// Line 154: {SITE_ADDRESS}
|
||||
{SITE_ADDRESS}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<br />
|
||||
// Line 156: {t('footer.hours', { hours: SITE_HOURS })}
|
||||
{t('footer.hours', { hours: SITE_HOURS })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</li>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</ul>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mt-12 flex flex-col items-center justify-between gap-4 border-t border-brand-800 pt-8 sm:flex-row">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-brand-300">
|
||||
// Line 165: © {new Date().getFullYear()} {t('site.name')}. {t('f...
|
||||
© {new Date().getFullYear()} {t('site.name')}. {t('footer.rights')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex gap-6 text-xs text-brand-300">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/about#privacy" className="hover:text-white">
|
||||
// Line 169: {t('nav.privacy')}
|
||||
{t('nav.privacy')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/about#terms" className="hover:text-white">
|
||||
// Line 172: {t('nav.terms')}
|
||||
{t('nav.terms')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</footer>
|
||||
// Line 178: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/layout/Header.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useState } from 'react';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { ShoppingCart, Heart, User, Menu, X, Search } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useCartStore } from '@/store/cart';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useWishlistStore } from '@/store/wishlist';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import Logo from '@/components/ui/Logo';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { SITE_LOCATION } from '@/lib/constants';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function Header() {
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const cartCount = useCartStore((s) => s.getItemCount());
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const wishlistCount = useWishlistStore((s) => s.items.length);
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 20: const navLinks = [
|
||||
const navLinks = [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ href: '/shop', label: t('nav.shop') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ href: '/shop?category=chicken', label: t('nav.chicken') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ href: '/shop?category=beef', label: t('nav.beef') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ href: '/shop?category=lamb', label: t('nav.lamb') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ href: '/shop?category=fish', label: t('nav.fish') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ href: '/about', label: t('nav.about') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ href: '/about#contact', label: t('nav.contact') },
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<header className="border-b border-cream-300/80 bg-cream-50/95 backdrop-blur-md">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex h-16 items-center justify-between lg:h-20">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href="/" className="flex items-center gap-3">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Logo size="sm" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="hidden sm:block">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="font-display text-xl font-bold text-brand-800">
|
||||
// Line 38: {t('site.name')}
|
||||
{t('site.name')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-[10px] font-medium uppercase tracking-widest text-brand-600">
|
||||
// Line 41: {SITE_LOCATION}
|
||||
{SITE_LOCATION}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<nav className="hidden items-center gap-8 lg:flex">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{navLinks.map((link) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Line 49: key={link.href}
|
||||
key={link.href}
|
||||
// Link target URL — internal route or external https://
|
||||
href={link.href}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="text-sm font-medium text-gray-600 transition-colors hover:text-brand-700"
|
||||
// Line 52: >
|
||||
>
|
||||
// Line 53: {link.label}
|
||||
{link.label}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// Line 55: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</nav>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-1 sm:gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/shop?q="
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="hidden rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-brand-700 sm:block"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.searchProducts')}
|
||||
// Line 63: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Search className="h-5 w-5" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/wishlist"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="relative rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.wishlist')}
|
||||
// Line 71: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Heart className="h-5 w-5" />
|
||||
// Line 73: {wishlistCount > 0 && (
|
||||
{wishlistCount > 0 && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="absolute -end-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-gold-500 text-[10px] font-bold text-white">
|
||||
// Line 75: {wishlistCount}
|
||||
{wishlistCount}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 77: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href="/cart"
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="relative rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.cart')}
|
||||
// Line 84: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
// Line 86: {cartCount > 0 && (
|
||||
{cartCount > 0 && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="absolute -end-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-brand-700 text-[10px] font-bold text-white">
|
||||
// Line 88: {cartCount}
|
||||
{cartCount}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 90: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={isAuthenticated ? '/account' : '/login'}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.account')}
|
||||
// Line 97: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<User className="h-5 w-5" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 lg:hidden"
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setMobileOpen(!mobileOpen)}
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.toggleMenu')}
|
||||
// Line 105: >
|
||||
>
|
||||
// Line 106: {mobileOpen ? <X className="h-5 w-5" /> : <Menu className...
|
||||
{mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 112: {mobileOpen && (
|
||||
{mobileOpen && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="border-t border-gray-100 bg-white lg:hidden">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<nav className="flex flex-col px-4 py-4">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{navLinks.map((link) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Line 117: key={link.href}
|
||||
key={link.href}
|
||||
// Link target URL — internal route or external https://
|
||||
href={link.href}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="rounded-lg px-4 py-3 text-sm font-medium text-gray-600 transition-colors hover:bg-brand-50 hover:text-brand-700"
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setMobileOpen(false)}
|
||||
// Line 121: >
|
||||
>
|
||||
// Line 122: {link.label}
|
||||
{link.label}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// Line 124: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</nav>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 127: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</header>
|
||||
// Line 129: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/layout/LanguageBanner.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Globe } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { LOCALES, Locale } from '@/i18n/types';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useLocaleStore } from '@/store/locale';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { cn } from '@/lib/utils';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function LanguageBanner() {
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { locale, setLocale } = useLocaleStore();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="border-b border-brand-800 bg-brand-950 text-white"
|
||||
// Line 16: role="navigation"
|
||||
role="navigation"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.language')}
|
||||
// Line 18: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mx-auto flex max-w-7xl flex-col items-center gap-2 px-4 py-2 sm:flex-row sm:justify-between sm:px-6 lg:px-8">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-brand-200">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Globe className="h-3.5 w-3.5 text-gold-400" aria-hidden />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>{t('languageBanner.choose')}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex w-full max-w-md items-center justify-center gap-1 rounded-full bg-brand-900 p-1 ring-1 ring-brand-800 sm:w-auto sm:max-w-none"
|
||||
// Line 27: role="group"
|
||||
role="group"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.language')}
|
||||
// Line 29: >
|
||||
>
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{LOCALES.map((lang) => {
|
||||
// Line 31: const active = locale === lang.code;
|
||||
const active = locale === lang.code;
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 34: key={lang.code}
|
||||
key={lang.code}
|
||||
// Line 35: type="button"
|
||||
type="button"
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setLocale(lang.code as Locale)}
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-pressed={active}
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={`${lang.label} (${lang.nativeLabel})`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'flex min-w-0 flex-1 items-center justify-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-semibold transition-all sm:flex-initial sm:px-4 sm:py-2 sm:text-sm',
|
||||
// Line 41: active
|
||||
active
|
||||
// Line 42: ? 'bg-gold-500 text-brand-950 shadow-gold'
|
||||
? 'bg-gold-500 text-brand-950 shadow-gold'
|
||||
// Line 43: : 'text-brand-200 hover:bg-brand-800 hover:text-white'
|
||||
: 'text-brand-200 hover:bg-brand-800 hover:text-white'
|
||||
// Line 44: )}
|
||||
)}
|
||||
// Line 45: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className={lang.code === 'ur' ? 'font-[family-name:var(--font-urdu)]' : ''}>
|
||||
// Line 47: {lang.nativeLabel}
|
||||
{lang.nativeLabel}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'hidden text-[10px] font-normal uppercase tracking-wide sm:inline',
|
||||
// Line 52: active ? 'text-brand-900/70' : 'text-brand-400'
|
||||
active ? 'text-brand-900/70' : 'text-brand-400'
|
||||
// Line 53: )}
|
||||
)}
|
||||
// Line 54: >
|
||||
>
|
||||
// Line 55: {lang.label}
|
||||
{lang.label}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 58: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
})}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 63: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/layout/LanguageSwitcher.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Globe, Check } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { LOCALES, Locale } from '@/i18n/types';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useLocaleStore } from '@/store/locale';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { cn } from '@/lib/utils';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function LanguageSwitcher() {
|
||||
// React useState — local component state that triggers re-render on change
|
||||
const [open, setOpen] = useState(false);
|
||||
// Line 12: const ref = useRef<HTMLDivElement>(null);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { locale, setLocale } = useLocaleStore();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Array.find — get first matching item or undefined
|
||||
const current = LOCALES.find((l) => l.code === locale);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Side effect hook — runs after paint; deps array controls when it re-runs
|
||||
useEffect(() => {
|
||||
// Function declaration — reusable logic in this file
|
||||
function handleClick(e: MouseEvent) {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
// Line 21: setOpen(false);
|
||||
setOpen(false);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Line 24: document.addEventListener('mousedown', handleClick);
|
||||
document.addEventListener('mousedown', handleClick);
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return () => document.removeEventListener('mousedown', handleClick);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}, []);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div ref={ref} className="relative">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => setOpen(!open)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex items-center gap-1.5 rounded-lg px-2 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-brand-700"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('nav.language')}
|
||||
// Line 34: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Globe className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="hidden sm:inline">{current?.nativeLabel}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 39: {open && (
|
||||
{open && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="absolute end-0 top-full z-50 mt-1 min-w-[10rem] overflow-hidden rounded-xl border border-gray-100 bg-white py-1 shadow-premium-lg">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{LOCALES.map((lang) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 43: key={lang.code}
|
||||
key={lang.code}
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => {
|
||||
// Line 45: setLocale(lang.code as Locale);
|
||||
setLocale(lang.code as Locale);
|
||||
// Line 46: setOpen(false);
|
||||
setOpen(false);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'flex w-full items-center justify-between gap-3 px-4 py-2.5 text-sm transition-colors hover:bg-brand-50',
|
||||
// Line 50: locale === lang.code
|
||||
locale === lang.code
|
||||
// Line 51: ? 'font-semibold text-brand-700'
|
||||
? 'font-semibold text-brand-700'
|
||||
// Line 52: : 'text-gray-600'
|
||||
: 'text-gray-600'
|
||||
// Line 53: )}
|
||||
)}
|
||||
// Line 54: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span>
|
||||
// Line 56: {lang.nativeLabel}
|
||||
{lang.nativeLabel}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="ms-2 text-xs text-gray-400">{lang.label}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 59: {locale === lang.code && (
|
||||
{locale === lang.code && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Check className="h-4 w-4 shrink-0 text-brand-700" />
|
||||
// Line 61: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 63: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 65: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 67: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/layout/LocaleAttributes.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useEffect } from 'react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useLocaleStore } from '@/store/locale';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { getHtmlLang, isRtl } from '@/i18n';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function LocaleAttributes() {
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const locale = useLocaleStore((s) => s.locale);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Side effect hook — runs after paint; deps array controls when it re-runs
|
||||
useEffect(() => {
|
||||
// Line 11: document.documentElement.lang = getHtmlLang(locale);
|
||||
document.documentElement.lang = getHtmlLang(locale);
|
||||
// Line 12: document.documentElement.dir = isRtl(locale) ? 'rtl' : 'l...
|
||||
document.documentElement.dir = isRtl(locale) ? 'rtl' : 'ltr';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}, [locale]);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return null;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/product/CustomizationSelector.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Category, ProductCustomization } from '@/types';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { CUT_COUNTS, CUTTING_STYLE_KEYS } from '@/lib/constants';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { updateMeatCustomization } from '@/lib/customization';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { cn } from '@/lib/utils';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Scissors, Hash } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface CustomizationSelectorProps {
|
||||
// Line 11: category: Category;
|
||||
category: Category;
|
||||
// Line 12: customization: ProductCustomization;
|
||||
customization: ProductCustomization;
|
||||
// Line 13: onChange: (customization: ProductCustomization) => void;
|
||||
onChange: (customization: ProductCustomization) => void;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function CustomizationSelector({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
onChange,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}: CustomizationSelectorProps) {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (category === 'fish') {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="rounded-xl border border-brand-100 bg-brand-50/50 p-4">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-sm text-brand-700">{t('product.fishNote')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 28: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 31: const meat = updateMeatCustomization(customization, categ...
|
||||
const meat = updateMeatCustomization(customization, category, {});
|
||||
// Line 32: const selectedCuts = meat.cuts;
|
||||
const selectedCuts = meat.cuts;
|
||||
// Line 33: const selectedStyle = meat.cuttingStyle;
|
||||
const selectedStyle = meat.cuttingStyle;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Hash className="h-4 w-4 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="text-sm font-semibold text-brand-900">
|
||||
// Line 41: {t('product.howManyCuts')}
|
||||
{t('product.howManyCuts')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-wrap gap-2">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{CUT_COUNTS.map((cuts) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 47: key={cuts}
|
||||
key={cuts}
|
||||
// Line 48: type="button"
|
||||
type="button"
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() =>
|
||||
// Line 50: onChange(updateMeatCustomization(customization, category,...
|
||||
onChange(updateMeatCustomization(customization, category, { cuts }))
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'min-w-[3.5rem] rounded-lg border-2 px-4 py-2.5 text-sm font-semibold transition-all',
|
||||
// Line 54: selectedCuts === cuts
|
||||
selectedCuts === cuts
|
||||
// Line 55: ? 'border-brand-700 bg-brand-700 text-white shadow-premium'
|
||||
? 'border-brand-700 bg-brand-700 text-white shadow-premium'
|
||||
// Line 56: : 'border-gray-200 bg-white text-gray-700 hover:border-br...
|
||||
: 'border-gray-200 bg-white text-gray-700 hover:border-brand-300 hover:bg-brand-50'
|
||||
// Line 57: )}
|
||||
)}
|
||||
// Line 58: >
|
||||
>
|
||||
// Line 59: {cuts}
|
||||
{cuts}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 61: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-2 text-xs text-gray-500">{t('product.howManyCutsHint')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Scissors className="h-4 w-4 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="text-sm font-semibold text-brand-900">
|
||||
// Line 70: {t('product.selectCutting')}
|
||||
{t('product.selectCutting')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{CUTTING_STYLE_KEYS.map((style) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 76: key={style}
|
||||
key={style}
|
||||
// Line 77: type="button"
|
||||
type="button"
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() =>
|
||||
// Line 79: onChange(
|
||||
onChange(
|
||||
// Line 80: updateMeatCustomization(customization, category, { cuttin...
|
||||
updateMeatCustomization(customization, category, { cuttingStyle: style })
|
||||
// Line 81: )
|
||||
)
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'rounded-lg border-2 px-4 py-3 text-start text-sm font-medium transition-all',
|
||||
// Line 85: selectedStyle === style
|
||||
selectedStyle === style
|
||||
// Line 86: ? 'border-brand-700 bg-brand-700 text-white shadow-premium'
|
||||
? 'border-brand-700 bg-brand-700 text-white shadow-premium'
|
||||
// Line 87: : 'border-gray-200 bg-white text-gray-700 hover:border-br...
|
||||
: 'border-gray-200 bg-white text-gray-700 hover:border-brand-300 hover:bg-brand-50'
|
||||
// Line 88: )}
|
||||
)}
|
||||
// Line 89: >
|
||||
>
|
||||
// Line 90: {t(`cutting.${style}`)}
|
||||
{t(`cutting.${style}`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 92: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
// Line 95: {t('product.selectCuttingHint', {
|
||||
{t('product.selectCuttingHint', {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: t(`categories.${category}.name`),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
})}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 101: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/product/ProductCard.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Next.js Link — fast client-side navigation without full page reload
|
||||
import Link from 'next/link';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { Heart, ShoppingCart } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { LocalizedProduct } from '@/lib/product-i18n';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { formatPrice, getFormatLocale } from '@/lib/utils';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useWishlistStore } from '@/store/wishlist';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { cn } from '@/lib/utils';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { products } from '@/lib/products';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface ProductCardProps {
|
||||
// Line 14: product: LocalizedProduct;
|
||||
product: LocalizedProduct;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function ProductCard({ product }: ProductCardProps) {
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const { isInWishlist, toggleItem } = useWishlistStore();
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t, locale } = useTranslation();
|
||||
// Array.find — get first matching item or undefined
|
||||
const rawProduct = products.find((p) => p.id === product.id)!;
|
||||
// Line 21: const inWishlist = isInWishlist(product.id);
|
||||
const inWishlist = isInWishlist(product.id);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="card-premium group overflow-hidden">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="relative aspect-[4/3] overflow-hidden bg-gray-50">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href={`/product/${product.slug}`}>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 28: src={product.image}
|
||||
src={product.image}
|
||||
// Line 29: alt={product.name}
|
||||
alt={product.name}
|
||||
// Line 30: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
// Line 32: sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw...
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 320px"
|
||||
// Line 33: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// Line 35: {product.badge && (
|
||||
{product.badge && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="absolute start-3 top-3 rounded-full bg-gold-500 px-3 py-1 text-xs font-semibold text-white shadow-gold">
|
||||
// Line 37: {product.badge}
|
||||
{product.badge}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 39: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => toggleItem(rawProduct)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'absolute end-3 top-3 flex h-9 w-9 items-center justify-center rounded-full bg-white/90 shadow-sm backdrop-blur transition-all hover:scale-110',
|
||||
// Line 44: inWishlist && 'text-red-500'
|
||||
inWishlist && 'text-red-500'
|
||||
// Line 45: )}
|
||||
)}
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={inWishlist ? t('product.removeWishlist') : t('product.addWishlist')}
|
||||
// Line 47: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Heart className={cn('h-4 w-4', inWishlist && 'fill-current')} />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="p-5">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-xs font-medium uppercase tracking-wider text-brand-600">
|
||||
// Line 55: {t(`categories.${product.category}.name`)}
|
||||
{t(`categories.${product.category}.name`)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// Line 57: {product.weight && (
|
||||
{product.weight && (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-xs text-gray-400">· {product.weight}</span>
|
||||
// Line 59: )}
|
||||
)}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link href={`/product/${product.slug}`}>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h3 className="mb-1 font-display text-lg font-semibold text-brand-900 transition-colors group-hover:text-brand-700">
|
||||
// Line 64: {product.name}
|
||||
{product.name}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</h3>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="mb-4 line-clamp-2 text-sm text-gray-500">{product.description}</p>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex items-center justify-between">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="text-lg font-bold text-brand-800">
|
||||
// Line 73: {formatPrice(product.price, getFormatLocale(locale))}
|
||||
{formatPrice(product.price, getFormatLocale(locale))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<span className="ms-1 text-xs text-gray-400">{product.priceUnit}</span>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<Link
|
||||
// Link target URL — internal route or external https://
|
||||
href={`/product/${product.slug}`}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-brand-700 text-white transition-all hover:bg-brand-800 hover:shadow-premium"
|
||||
// Accessibility attribute — screen readers and assistive tech
|
||||
aria-label={t('product.viewProduct', { name: product.name })}
|
||||
// Line 82: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<ShoppingCart className="h-4 w-4" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</Link>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 88: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/shop/ShopFilters.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Category, SortOption } from '@/types';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { cn } from '@/lib/utils';
|
||||
// Lucide icons — lightweight SVG icon components
|
||||
import { SlidersHorizontal } from 'lucide-react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface ShopFiltersProps {
|
||||
// Line 9: selectedCategory: Category | 'all';
|
||||
selectedCategory: Category | 'all';
|
||||
// Line 10: onCategoryChange: (category: Category | 'all') => void;
|
||||
onCategoryChange: (category: Category | 'all') => void;
|
||||
// Line 11: sortBy: SortOption;
|
||||
sortBy: SortOption;
|
||||
// Line 12: onSortChange: (sort: SortOption) => void;
|
||||
onSortChange: (sort: SortOption) => void;
|
||||
// Line 13: searchQuery: string;
|
||||
searchQuery: string;
|
||||
// Line 14: onSearchChange: (query: string) => void;
|
||||
onSearchChange: (query: string) => void;
|
||||
// Line 15: totalResults: number;
|
||||
totalResults: number;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function ShopFilters({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
selectedCategory,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
onCategoryChange,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortBy,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
onSortChange,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
searchQuery,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
onSearchChange,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
totalResults,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}: ShopFiltersProps) {
|
||||
// Custom hook — returns t() translator and current locale
|
||||
const { t } = useTranslation();
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 29: const categories: { value: Category | 'all'; label: strin...
|
||||
const categories: { value: Category | 'all'; label: string }[] = [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'all', label: t('shop.all') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'chicken', label: t('nav.chicken') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'beef', label: t('nav.beef') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'lamb', label: t('nav.lamb') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'fish', label: t('nav.fish') },
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 37: const sortOptions: { value: SortOption; label: string }[]...
|
||||
const sortOptions: { value: SortOption; label: string }[] = [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'featured', label: t('shop.sortFeatured') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'price-asc', label: t('shop.sortPriceAsc') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'price-desc', label: t('shop.sortPriceDesc') },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ value: 'name', label: t('shop.sortName') },
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="space-y-6">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<SlidersHorizontal className="h-4 w-4 text-brand-700" />
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<h2 className="text-sm font-semibold text-brand-900">{t('shop.filters')}</h2>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="text-xs text-gray-500">
|
||||
// Line 52: {t('shop.productsFound', { count: totalResults })}
|
||||
{t('shop.productsFound', { count: totalResults })}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label htmlFor="search" className="label-text">
|
||||
// Line 58: {t('shop.search')}
|
||||
{t('shop.search')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<input
|
||||
// Line 61: id="search"
|
||||
id="search"
|
||||
// Line 62: type="text"
|
||||
type="text"
|
||||
// Line 63: placeholder={t('shop.searchPlaceholder')}
|
||||
placeholder={t('shop.searchPlaceholder')}
|
||||
// Line 64: value={searchQuery}
|
||||
value={searchQuery}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 67: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<p className="label-text">{t('shop.category')}</p>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div className="flex flex-wrap gap-2">
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{categories.map((cat) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 75: key={cat.value}
|
||||
key={cat.value}
|
||||
// Click handler — runs when user clicks (must be client component)
|
||||
onClick={() => onCategoryChange(cat.value)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'rounded-full px-4 py-2 text-sm font-medium transition-all',
|
||||
// Line 79: selectedCategory === cat.value
|
||||
selectedCategory === cat.value
|
||||
// Line 80: ? 'bg-brand-700 text-white shadow-premium'
|
||||
? 'bg-brand-700 text-white shadow-premium'
|
||||
// Line 81: : 'bg-gray-100 text-gray-600 hover:bg-brand-50 hover:text...
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-brand-50 hover:text-brand-700'
|
||||
// Line 82: )}
|
||||
)}
|
||||
// Line 83: >
|
||||
>
|
||||
// Line 84: {cat.label}
|
||||
{cat.label}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 86: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<label htmlFor="sort" className="label-text">
|
||||
// Line 92: {t('shop.sortBy')}
|
||||
{t('shop.sortBy')}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</label>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<select
|
||||
// Line 95: id="sort"
|
||||
id="sort"
|
||||
// Line 96: value={sortBy}
|
||||
value={sortBy}
|
||||
// Change handler — runs when input/select value changes
|
||||
onChange={(e) => onSortChange(e.target.value as SortOption)}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="input-field"
|
||||
// Line 99: >
|
||||
>
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
{sortOptions.map((opt) => (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<option key={opt.value} value={opt.value}>
|
||||
// Line 102: {opt.label}
|
||||
{opt.label}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</option>
|
||||
// Line 104: ))}
|
||||
))}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</select>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 108: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/ui/AppImage.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js Image — optimized images (lazy load, WebP/AVIF)
|
||||
import Image, { ImageProps } from 'next/image';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { IMAGE_QUALITY } from '@/lib/images';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript type alias — union or shorthand for complex types
|
||||
type AppImageProps = ImageProps & {
|
||||
// Line 5: quality?: number;
|
||||
quality?: number;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Block comment — documents the file or function below
|
||||
/** Site-wide Image wrapper with consistent high quality defaults */
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function AppImage({ quality = IMAGE_QUALITY, ...props }: AppImageProps) {
|
||||
// Return value from function
|
||||
return <Image quality={quality} {...props} />;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/ui/Button.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { cn } from '@/lib/utils';
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { ButtonHTMLAttributes, forwardRef } from 'react';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
// Line 5: variant?: 'primary' | 'secondary' | 'gold' | 'ghost';
|
||||
variant?: 'primary' | 'secondary' | 'gold' | 'ghost';
|
||||
// Line 6: size?: 'sm' | 'md' | 'lg';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 9: const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
// Line 10: ({ className, variant = 'primary', size = 'md', children,...
|
||||
({ className, variant = 'primary', size = 'md', children, ...props }, ref) => {
|
||||
// Line 11: const variants = {
|
||||
const variants = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
primary: 'btn-primary',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
secondary: 'btn-secondary',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
gold: 'btn-gold',
|
||||
// Line 15: ghost:
|
||||
ghost:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-brand-700',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 19: const sizes = {
|
||||
const sizes = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sm: 'px-4 py-2 text-xs',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
md: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lg: 'px-8 py-4 text-base',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<button
|
||||
// Line 27: ref={ref}
|
||||
ref={ref}
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(variants[variant], sizes[size], className)}
|
||||
// Line 29: {...props}
|
||||
{...props}
|
||||
// Line 30: >
|
||||
>
|
||||
// Line 31: {children}
|
||||
{children}
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</button>
|
||||
// Line 33: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Line 35: );
|
||||
);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 37: Button.displayName = 'Button';
|
||||
Button.displayName = 'Button';
|
||||
// Line 38: export default Button;
|
||||
export default Button;
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/components/ui/Logo.tsx
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { IMAGES } from '@/lib/images';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { cn } from '@/lib/utils';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface LogoProps {
|
||||
// Line 6: size?: 'sm' | 'md' | 'lg';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
// Line 7: className?: string;
|
||||
className?: string;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 10: const sizes = {
|
||||
const sizes = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sm: 'h-10 w-10',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
md: 'h-12 w-12',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lg: 'h-20 w-20',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Default export — main component/page Next.js or other files import
|
||||
export default function Logo({ size = 'md', className }: LogoProps) {
|
||||
// Return JSX — describes UI tree React renders to the DOM
|
||||
return (
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<div
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className={cn(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'relative shrink-0 overflow-hidden rounded-full ring-2 ring-cream-200',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sizes[size],
|
||||
// Line 22: className
|
||||
className
|
||||
// Line 23: )}
|
||||
)}
|
||||
// Line 24: >
|
||||
>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
<AppImage
|
||||
// Line 26: src={IMAGES.logo}
|
||||
src={IMAGES.logo}
|
||||
// Line 27: alt="Kött Gård"
|
||||
alt="Kött Gård"
|
||||
// Line 28: fill
|
||||
fill
|
||||
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
||||
className="object-cover"
|
||||
// Line 30: sizes="(max-width: 640px) 40px, 80px"
|
||||
sizes="(max-width: 640px) 40px, 80px"
|
||||
// Line 31: priority
|
||||
priority
|
||||
// Line 32: quality={95}
|
||||
quality={95}
|
||||
// Line 33: />
|
||||
/>
|
||||
// JSX element — HTML-like tag becomes React component in browser
|
||||
</div>
|
||||
// Line 35: );
|
||||
);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/hooks/useTranslation.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
||||
'use client';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Import React — core UI library (components, hooks, JSX)
|
||||
import { useMemo } from 'react';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { createTranslator } from '@/i18n';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { useLocaleStore } from '@/store/locale';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function useTranslation() {
|
||||
// Zustand selector — subscribe to slice of global store
|
||||
const locale = useLocaleStore((s) => s.locale);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// React useMemo — cache expensive computed value until dependencies change
|
||||
const t = useMemo(() => createTranslator(locale), [locale]);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return { t, locale };
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/i18n/index.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import from a relative file in the same project
|
||||
import { Locale, TranslationDict } from './types';
|
||||
// Import from a relative file in the same project
|
||||
import { en } from './locales/en';
|
||||
// Import from a relative file in the same project
|
||||
import { sv } from './locales/sv';
|
||||
// Import from a relative file in the same project
|
||||
import { ur } from './locales/ur';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 6: const dictionaries: Record<Locale, TranslationDict> = { e...
|
||||
const dictionaries: Record<Locale, TranslationDict> = { en, sv, ur };
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Function declaration — reusable logic in this file
|
||||
function resolve(obj: TranslationDict, path: string): string {
|
||||
// Line 9: const keys = path.split('.');
|
||||
const keys = path.split('.');
|
||||
// Line 10: let current: string | TranslationDict = obj;
|
||||
let current: string | TranslationDict = obj;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 12: for (const key of keys) {
|
||||
for (const key of keys) {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (typeof current !== 'object' || current === null || !(key in current)) {
|
||||
// Return value from function
|
||||
return path;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Line 16: current = current[key];
|
||||
current = current[key];
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return typeof current === 'string' ? current : path;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function createTranslator(locale: Locale) {
|
||||
// Line 23: const dict = dictionaries[locale] ?? dictionaries.en;
|
||||
const dict = dictionaries[locale] ?? dictionaries.en;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return function t(path: string, params?: Record<string, string | number>): string {
|
||||
// Line 26: let text = resolve(dict, path);
|
||||
let text = resolve(dict, path);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (params) {
|
||||
// Line 29: Object.entries(params).forEach(([key, value]) => {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
// Line 30: text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), Stri...
|
||||
text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value));
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return text;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function isRtl(locale: Locale): boolean {
|
||||
// Return value from function
|
||||
return locale === 'ur';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getHtmlLang(locale: Locale): string {
|
||||
// Line 43: const map: Record<Locale, string> = { en: 'en', sv: 'sv',...
|
||||
const map: Record<Locale, string> = { en: 'en', sv: 'sv', ur: 'ur' };
|
||||
// Return value from function
|
||||
return map[locale];
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,934 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/i18n/locales/en.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import external package or local module
|
||||
import { TranslationDict } from '../types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const en: TranslationDict = {
|
||||
// Line 4: site: {
|
||||
site: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Kött Gård',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tagline: 'Premium Halal',
|
||||
// Line 7: description:
|
||||
description:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium 100% Halal meat delivery. Fresh and frozen chicken, beef, lamb, and fish — customized to your preference and delivered to your door.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
metaTitle: 'Premium Halal Meat Delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
initials: 'KG',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'hello@kottgard.se',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
demoEmail: 'demo@kottgard.se',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 14: nav: {
|
||||
nav: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'Shop',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chicken: 'Chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beef: 'Beef',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lamb: 'Lamb',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fish: 'Fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
about: 'About Us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalCert: 'Halal Certification',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery: 'Delivery Info',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contact: 'Contact',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
myAccount: 'My Account',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderHistory: 'Order History',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
wishlist: 'Wishlist',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cart: 'Cart',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
privacy: 'Privacy Policy',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
terms: 'Terms of Service',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
searchProducts: 'Search products',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
toggleMenu: 'Toggle menu',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
account: 'Account',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
language: 'Language',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 35: languageBanner: {
|
||||
languageBanner: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
choose: 'Choose your language',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 38: hero: {
|
||||
hero: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badge: '100% Halal Certified',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
taglineShort: 'Naturally Pure',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Premium Halal Meat',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
titleHighlight: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
titleEnd: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitleShort: 'Fresh. Quality. Reliable.',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Halal certified · Fresh daily · Home delivery · Open every day',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hours: 'Open every day {hours}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
location: 'Tingvallavägen 11, Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shopNow: 'Browse Our Selection',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
browseChicken: 'Browse Chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'Order via WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 53: aboutPreview: {
|
||||
aboutPreview: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'About Us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: "Märsta's Finest Butcher Shop",
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p1: 'Kött Gård is more than a butcher shop — we are a promise of quality. All our meat is 100% Halal certified and delivered fresh every day.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p2: 'We source lamb from Ireland and New Zealand, chicken and beef from reputable producers, and help you find the right cut for dinner, celebrations, or Sunday roast.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p3: 'Visit us at Tingvallavägen, tell us what you are looking for — we cut and pack to your specifications.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statHalal: '100%',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statHalalLabel: 'Halal certified',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDays: '7 days',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDaysLabel: 'Open weekly',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDelivery: 'Daily',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDeliveryLabel: 'Delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statFresh: 'Fresh',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statFreshLabel: 'Every day',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
readMore: 'Read more about us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
imageAlt: 'Fresh meat cuts on a cutting board from Kött Gård',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 70: trust: {
|
||||
trust: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halal: '100% Halal',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalDesc: 'Certified halal sourcing with full traceability and compliance.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fresh: 'Fresh Daily',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDesc: 'Sourced fresh every morning and delivered at peak quality.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premium: 'Premium Quality',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumDesc: 'Hand-selected cuts from trusted farms, prepared by expert butchers.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 78: categories: {
|
||||
categories: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Our Selection',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'Hand-picked meat — every day. Fresh delivery. Halal. Cut to order.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'Shop {name}',
|
||||
// Line 82: chicken: {
|
||||
chicken: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Breast fillet, wings, drumsticks and whole chicken. Fresh every morning.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 86: beef: {
|
||||
beef: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Beef & Veal',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Mince, bone marrow and premium cuts. High marbling, consistent quality.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 90: lamb: {
|
||||
lamb: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lamb',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Shoulder, neck, roast and rack. From Ireland and New Zealand.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 94: fish: {
|
||||
fish: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Fresh catch, cleaned and ready to cook.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 99: featured: {
|
||||
featured: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Curated Selection',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Featured Products',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'Our most popular cuts, loved by families across the city.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewAll: 'View All',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 105: howItWorks: {
|
||||
howItWorks: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Order',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'How easy it is to order',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step1Title: 'Contact us',
|
||||
// Line 109: step1Desc:
|
||||
step1Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Send us a WhatsApp message with what you want — we reply quickly.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step2Title: 'We confirm',
|
||||
// Line 112: step2Desc:
|
||||
step2Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'We confirm your order, give you the price, and tell you when it is ready.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step3Title: 'Pick up or delivery',
|
||||
// Line 115: step3Desc:
|
||||
step3Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Pick up in store at Tingvallavägen 11 or choose home delivery.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step: 'Step {n}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'Order on WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 120: offers: {
|
||||
offers: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Order',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Weekly offers',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'We update regularly with fresh deals. Follow us on social media for the latest prices.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
disclaimer: 'Price valid while supplies last',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
was: 'Was',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
now: 'NOW',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
order: 'Order',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewProduct: 'View product',
|
||||
// Line 130: badge: {
|
||||
badge: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fresh: 'FRESH',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halal: 'HALAL',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 134: items: {
|
||||
items: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chickenWings: { name: 'Fresh chicken wings PL' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lambSteak: { name: 'Fresh lamb roast Ireland' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beefMince: { name: 'Beef mince 5% fat IRL' },
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 140: social: {
|
||||
social: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Follow us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Follow us',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'1,100+ followers on Facebook · 249 posts · Daily updates',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
facebook: 'Facebook',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
instagram: 'Instagram',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 149: contact: {
|
||||
contact: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Contact',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Visit us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addressLabel: 'Address',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phoneLabel: 'Phone',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hoursLabel: 'Opening hours',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hoursValue: 'Every day: {hours}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
writeUs: 'Message us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
callUs: 'Call us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
openMaps: 'Open in Google Maps',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
learnMore: 'Contact details',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 161: cta: {
|
||||
cta: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Ready for Premium Halal Meat?',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Order today and experience the difference of truly fresh, customized halal meat delivered to your doorstep.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
button: 'Browse Our Selection',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'Order on WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 168: shop: {
|
||||
shop: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Shop All Products',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'Premium halal meat, customized to your preference',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noProducts: 'No products found',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noProductsHint: 'Try adjusting your filters or search query',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
filters: 'Filters',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productsFound: '{count} products found',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
search: 'Search',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
searchPlaceholder: 'Search products...',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'Category',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortBy: 'Sort By',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
all: 'All',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortFeatured: 'Featured',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortPriceAsc: 'Price: Low to High',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortPriceDesc: 'Price: High to Low',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortName: 'Name A–Z',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 185: product: {
|
||||
product: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
backToShop: 'Back to Shop',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: 'In Stock',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
outOfStock: 'Out of Stock',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalTrust: '100% Halal certified · Fresh daily · Premium quality',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
yourSelection: 'Your selection',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
aboutProduct: 'About This Product',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addToCart: 'Add to Cart',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addedToCart: 'Added to Cart',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
decreaseQty: 'Decrease quantity',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
increaseQty: 'Increase quantity',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
removeWishlist: 'Remove from wishlist',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addWishlist: 'Add to wishlist',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewProduct: 'View {name}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pieces: '{count} pieces',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
standardCut: 'Standard cut',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
howManyCuts: 'How Many Cuts Do You Want?',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
howManyCutsHint: 'Select the number of cuts for your order',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cutsAndStyle: '{cuts} cuts · {style}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
selectCutting: 'Select Cutting Style',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
selectCuttingHint: 'Our butchers will prepare your {category} exactly to your preferred cut',
|
||||
// Line 206: fishNote:
|
||||
fishNote:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Fish products are prepared with our standard professional cut — cleaned, scaled, and ready to cook.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 209: cutting: {
|
||||
cutting: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
nihari: 'Nihari cut',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
karahi: 'Karahi cut',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
qeema: 'Qeema (minced)',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
boneless: 'Boneless',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
steak: 'Steak cut',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 216: priceUnit: {
|
||||
priceUnit: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perBird: 'per bird',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perPack: 'per pack',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perKg: 'per kg',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 221: badges: {
|
||||
badges: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
bestseller: 'Bestseller',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chefsPick: "Chef's Pick",
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premium: 'Premium',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
popular: 'Popular',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshCatch: 'Fresh Catch',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 228: products: {
|
||||
products: {
|
||||
// Line 229: 'chicken-whole': {
|
||||
'chicken-whole': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Whole Chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Farm-fresh whole halal chicken, perfect for roasting or curry.',
|
||||
// Line 232: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Our whole chickens are sourced from certified halal farms and delivered at peak freshness. Each bird is hand-selected for quality, with tender meat and clean processing. Choose your preferred piece count and we will prepare it exactly how you need.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 235: 'chicken-breast': {
|
||||
'chicken-breast': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Chicken Breast',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Lean, boneless chicken breast — ideal for grilling and healthy meals.',
|
||||
// Line 238: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium boneless chicken breast, trimmed and ready to cook. Perfect for kebabs, stir-fries, and healthy weeknight dinners. Select your piece count and enjoy consistent quality every time.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 241: 'chicken-thighs': {
|
||||
'chicken-thighs': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Chicken Thighs',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Juicy halal chicken thighs with rich flavor for curries and grills.',
|
||||
// Line 244: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Our chicken thighs are known for their succulence and depth of flavor. Whether you are making a traditional karahi or a weekend BBQ, these thighs deliver every time.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 247: 'chicken-wings': {
|
||||
'chicken-wings': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Chicken Wings',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Party-ready halal chicken wings for frying, baking, or grilling.',
|
||||
// Line 250: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Crispy, flavorful chicken wings prepared halal and delivered fresh. A crowd favorite for game nights and family gatherings.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 253: 'beef-nihari': {
|
||||
'beef-nihari': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Beef for Nihari',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Slow-cook ready beef cuts, perfect for traditional nihari.',
|
||||
// Line 256: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Specially selected beef cuts ideal for slow-cooked nihari. Rich in collagen and flavor, these cuts break down beautifully over hours of simmering for an authentic, melt-in-your-mouth experience.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 259: 'beef-steak': {
|
||||
'beef-steak': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Premium Beef Steak',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Restaurant-quality halal steak cuts for the perfect sear.',
|
||||
// Line 262: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Hand-cut premium beef steaks from the finest halal sources. Marbled, tender, and ready for your grill or cast-iron pan. Choose your preferred cutting style.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 265: 'beef-mince': {
|
||||
'beef-mince': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Beef Mince',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Fresh halal beef mince for kebabs, burgers, and qeema.',
|
||||
// Line 268: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Finely ground halal beef mince with the perfect fat ratio for juicy kebabs, flavorful qeema, and homemade burgers. Ground fresh daily.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 271: 'beef-boneless': {
|
||||
'beef-boneless': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Boneless Beef Cubes',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Versatile boneless beef cubes for karahi, biryani, and stews.',
|
||||
// Line 274: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Uniform boneless beef cubes cut to perfection for quick-cooking dishes. Ideal for karahi, pulao, and stir-fries where consistent sizing matters.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 277: 'lamb-shoulder': {
|
||||
'lamb-shoulder': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lamb Shoulder',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Rich, flavorful lamb shoulder for slow roasts and curries.',
|
||||
// Line 280: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium halal lamb shoulder with beautiful marbling. Perfect for slow-roasted feasts, hearty curries, and traditional family meals. Customized to your preferred cut.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 283: 'lamb-leg': {
|
||||
'lamb-leg': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lamb Leg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Tender lamb leg for roasts, grills, and special occasions.',
|
||||
// Line 286: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Whole or portioned lamb leg from certified halal sources. A centerpiece cut for Eid celebrations, dinner parties, and Sunday roasts.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 289: 'lamb-chops': {
|
||||
'lamb-chops': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lamb Chops',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Premium lamb chops for grilling and fine dining at home.',
|
||||
// Line 292: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Thick-cut halal lamb chops with perfect fat caps for grilling. Restaurant quality, delivered to your kitchen.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 295: 'lamb-mince': {
|
||||
'lamb-mince': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lamb Mince',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Fresh halal lamb mince for kebabs, samosas, and qeema.',
|
||||
// Line 298: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Finely ground lamb mince with rich flavor. Essential for seekh kebabs, lamb qeema, and stuffed parathas.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 301: 'fish-salmon': {
|
||||
'fish-salmon': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Atlantic Salmon Fillet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Buttery salmon fillets, skin-on and ready to pan-sear.',
|
||||
// Line 304: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium Atlantic salmon fillets with rich omega-3 content. Cleaned, portioned, and vacuum-sealed for maximum freshness.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 307: 'fish-rohu': {
|
||||
'fish-rohu': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Rohu Fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Whole rohu fish, cleaned and scaled — a South Asian favorite.',
|
||||
// Line 310: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Fresh rohu fish, a staple in South Asian cuisine. Cleaned, scaled, and gutted. Perfect for fish curry, fried fish, and traditional recipes.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 313: 'fish-prawns': {
|
||||
'fish-prawns': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Jumbo Prawns',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Large shell-on prawns for grilling, curries, and biryanis.',
|
||||
// Line 316: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium jumbo prawns, deveined and ready to cook. Sweet, firm flesh that holds up beautifully in curries and tandoori preparations.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 319: 'fish-basa': {
|
||||
'fish-basa': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Basa Fillet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Mild, flaky basa fillets — perfect for beginners and kids.',
|
||||
// Line 322: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Boneless basa fillets with a mild, delicate flavor. Easy to cook and versatile — great for fish tacos, baking, and light curries.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 326: cart: {
|
||||
cart: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Your Cart',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
itemsCount: '{count} item(s) in your cart',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
empty: 'Your cart is empty',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
emptyHint: 'Browse our premium halal selection and add items to your cart.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
startShopping: 'Start Shopping',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization: 'Customization:',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderSummary: 'Order Summary',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtotal: 'Subtotal',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery: 'Delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
free: 'Free',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freeDeliveryHint: 'Free delivery on orders over 500 kr',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
total: 'Total',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
proceedCheckout: 'Proceed to Checkout',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
continueShopping: 'Continue Shopping',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
removeItem: 'Remove item',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 343: checkout: {
|
||||
checkout: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Secure Checkout',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
backToCart: 'Back to Cart',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noItems: 'No items to checkout',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
goToShop: 'Go to Shop',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderConfirmed: 'Order Confirmed!',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
thankYou: 'Thank you for your order. Your premium halal meat is being prepared.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderId: 'Order ID: {id}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewOrders: 'View Orders',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
haveAccount: 'Have an account?',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signIn: 'Sign in',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fasterCheckout: 'for faster checkout.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryDetails: 'Delivery Details',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fullName: 'Full Name',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'Email',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: 'Phone',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'Street Address',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'City',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'State',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: 'ZIP Code',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
payment: 'Payment',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
creditCard: 'Credit Card',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cashOnDelivery: 'Cash on Delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cardNumber: 'Card Number',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
expiry: 'Expiry',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cvv: 'CVV',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
qty: 'Qty: {count}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
processing: 'Processing...',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pay: 'Pay {amount}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
secure: 'Secure 256-bit SSL encryption',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 374: auth: {
|
||||
auth: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
welcomeBack: 'Welcome Back',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createAccount: 'Create Account',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
joinTagline: 'Join {name} for a premium shopping experience',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signInTagline: 'Sign in to your {name} account',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fullName: 'Full Name',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'Email',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
password: 'Password',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: 'Phone',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'Street Address',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'City',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'State',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: 'ZIP Code',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signIn: 'Sign In',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
register: 'Create Account',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hasAccount: 'Already have an account? Sign in',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noAccount: "Don't have an account? Register",
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
invalidCredentials: 'Invalid email or password. Try {email} / demo123',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
demo: 'Demo: {email} / demo123',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 394: account: {
|
||||
account: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'My Account',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
welcome: 'Welcome back, {name}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
memberSince: 'Member since {date}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
myWishlist: 'My Wishlist',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signOut: 'Sign Out',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderHistory: 'Order History',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noOrders: 'No orders yet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
startShopping: 'Start Shopping',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 404: wishlist: {
|
||||
wishlist: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'My Wishlist',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
saved: '{count} saved item(s)',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
empty: 'Your wishlist is empty',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
emptyHint: 'Save your favorite products to buy them later.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
browse: 'Browse Products',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 411: about: {
|
||||
about: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'About {name}',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Bringing premium, 100% Halal meat to your table — fresh, customized, and delivered with care.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
ourStory: 'Our Story',
|
||||
// Line 416: storyP1:
|
||||
storyP1:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'{name} was founded with a simple mission: make premium halal meat accessible to every family, without compromising on quality, freshness, or religious compliance. We understand that for many households, the right cut prepared the right way is not a luxury — it is essential.',
|
||||
// Line 418: storyP2:
|
||||
storyP2:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'From selecting piece counts for chicken to choosing Nihari or Karahi cuts for beef and lamb, we put customization at the heart of every order. Our expert butchers prepare each order by hand, and our temperature-controlled delivery ensures your meat arrives as fresh as the day it was cut.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalTitle: 'Halal Certification',
|
||||
// Line 421: halalDesc:
|
||||
halalDesc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Every product at {name} is sourced from certified halal suppliers. Our supply chain is fully traceable, and we maintain strict compliance with halal slaughter and processing standards. We work exclusively with farms and processors that share our commitment to ethical, religiously compliant meat production.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDaily: 'Fresh Daily',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDailyDesc: 'Sourced every morning from trusted farms',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumQuality: 'Premium Quality',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumQualityDesc: 'Hand-selected cuts by expert butchers',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fastDelivery: 'Fast Delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fastDeliveryDesc: 'Temperature-controlled same-day delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryTitle: 'Delivery Information',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery1: 'We deliver within a 25-mile radius of our processing facility.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery2: 'Orders placed before 2 PM are eligible for same-day delivery.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery3: 'Free delivery on orders over 500 kr. Standard delivery fee: 49 kr.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery4: 'All products are vacuum-sealed and transported in insulated packaging.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contactTitle: 'Contact Us',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
address: 'Tingvallavägen 11, 195 31 Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
privacyTitle: 'Privacy Policy',
|
||||
// Line 437: privacyText:
|
||||
privacyText:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'We collect only the information needed to process your orders and improve our service — name, contact details, and delivery address. We do not sell your data to third parties. Payment details are handled securely by our payment partners.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
termsTitle: 'Terms of Service',
|
||||
// Line 440: termsText:
|
||||
termsText:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'All prices are shown in SEK and may change without notice. Orders are subject to availability. Halal certification applies to all meat products listed. Delivery times are estimates and may vary during peak periods.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 443: footer: {
|
||||
footer: {
|
||||
// Line 444: tagline:
|
||||
tagline:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium 100% Halal meat delivery. Fresh, customized cuts delivered to your door with uncompromising quality.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'Shop',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
company: 'Company',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contact: 'Contact',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
rights: 'All rights reserved.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: '072-585 50 50',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hours: 'Open every day {hours}',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 453: notFound: {
|
||||
notFound: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: '404',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
message: 'Page not found',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
goHome: 'Go Home',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 458: orderStatus: {
|
||||
orderStatus: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pending: 'pending',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
confirmed: 'confirmed',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
preparing: 'preparing',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'out-for-delivery': 'out for delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivered: 'delivered',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
@@ -0,0 +1,934 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/i18n/locales/sv.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import external package or local module
|
||||
import { TranslationDict } from '../types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const sv: TranslationDict = {
|
||||
// Line 4: site: {
|
||||
site: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Kött Gård',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tagline: 'Premium Halal',
|
||||
// Line 7: description:
|
||||
description:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium 100 % halal köttleverans. Färskt och fryst kyckling, nötkött, lamm och fisk — anpassat efter dina önskemål och levererat till din dörr.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
metaTitle: 'Premium Halal Köttleverans',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
initials: 'KG',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'hello@kottgard.se',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
demoEmail: 'demo@kottgard.se',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 14: nav: {
|
||||
nav: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'Butik',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chicken: 'Kyckling',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beef: 'Nötkött',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lamb: 'Lamm',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fish: 'Fisk',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
about: 'Om oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalCert: 'Halalcertifiering',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery: 'Leveransinfo',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contact: 'Kontakt',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
myAccount: 'Mitt konto',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderHistory: 'Orderhistorik',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
wishlist: 'Önskelista',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cart: 'Varukorg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
privacy: 'Integritetspolicy',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
terms: 'Användarvillkor',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
searchProducts: 'Sök produkter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
toggleMenu: 'Växla meny',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
account: 'Konto',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
language: 'Språk',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 35: languageBanner: {
|
||||
languageBanner: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
choose: 'Välj språk',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 38: hero: {
|
||||
hero: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badge: '100 % Halal-certifierat',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
taglineShort: 'Naturligt Rent',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Premium Halal Kött',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
titleHighlight: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
titleEnd: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitleShort: 'Färskt. Kvalitet. Tillförlitligt.',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Halal-certifierat · Färskt dagligen · Hemleverans · Öppet alla dagar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hours: 'Öppet alla dagar {hours}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
location: 'Tingvallavägen 11, Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shopNow: 'Se vårt sortiment',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
browseChicken: 'Bläddra kyckling',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'Beställ via WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 53: aboutPreview: {
|
||||
aboutPreview: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Om oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Märstas finaste köttbutik',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p1: 'Kött Gård är mer än en köttbutik — vi är ett löfte om kvalitet. Allt vårt kött är 100 % Halal-certifierat och färskt levererat varje dag.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p2: 'Vi handlar lamm från Irland och Nya Zeeland, kyckling och nötkött från välrenommerade producenter, och hjälper dig hitta rätt detalj för middagen, festen eller söndagssteken.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p3: 'Kom in i butiken på Tingvallavägen, prata med oss om vad du letar efter — vi styckar och packar efter dina önskemål.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statHalal: '100 %',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statHalalLabel: 'Halal-certifierat',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDays: '7 dagar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDaysLabel: 'Öppet i veckan',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDelivery: 'Daglig',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDeliveryLabel: 'Leverans',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statFresh: 'Färskt',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statFreshLabel: 'Varje dag',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
readMore: 'Läs mer om oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
imageAlt: 'Färska köttdetaljer på skärbräda från Kött Gård',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 70: trust: {
|
||||
trust: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halal: '100 % Halal',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalDesc: 'Certifierad halal-källa med full spårbarhet och efterlevnad.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fresh: 'Färskt Dagligen',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDesc: 'Hämtas färskt varje morgon och levereras i toppskick.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premium: 'Premiumkvalitet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumDesc: 'Handplockade styckningar från betrodda gårdar, förberedda av expertslaktare.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 78: categories: {
|
||||
categories: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Vårt sortiment',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'Handplockat kött — varje dag. Färskt levererat. Halal. Styckat efter önskemål.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'Handla {name}',
|
||||
// Line 82: chicken: {
|
||||
chicken: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Kyckling',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Kycklingbröstfilé, vingar, klubba och hel kyckling. Färsk varje morgon.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 86: beef: {
|
||||
beef: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Nötkött & Kalv',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Nötfärs, kalv bone marrow och premiumskär. Hög marmorering, jämn kvalitet.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 90: lamb: {
|
||||
lamb: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lamm',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Lammbringa, lammhals, lammstek och lammrygg. Från Irland och Nya Zeeland.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 94: fish: {
|
||||
fish: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Fisk',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Färsk fångst, rengjord och redo att tillagas.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 99: featured: {
|
||||
featured: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Utvalt Sortiment',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Utvalda Produkter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'Våra mest populära styckningar, älskade av familjer i hela staden.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewAll: 'Visa alla',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 105: howItWorks: {
|
||||
howItWorks: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Beställ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Så enkelt beställer du',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step1Title: 'Kontakta oss',
|
||||
// Line 109: step1Desc:
|
||||
step1Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Skicka ett meddelande på WhatsApp med vad du vill ha — vi svarar snabbt.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step2Title: 'Vi bekräftar',
|
||||
// Line 112: step2Desc:
|
||||
step2Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Vi bekräftar din beställning, ger pris och säger när den är klar.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step3Title: 'Hämta eller leverans',
|
||||
// Line 115: step3Desc:
|
||||
step3Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Hämta i butik på Tingvallavägen 11 eller välj hemleverans.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step: 'Steg {n}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'Beställ på WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 120: offers: {
|
||||
offers: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Beställ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Veckans erbjudanden',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Vi uppdaterar löpande med färska erbjudanden. Följ oss på sociala medier för senaste priserna.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
disclaimer: 'Pris gäller så långt lagret räcker',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
was: 'Före',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
now: 'NU',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
order: 'Beställ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewProduct: 'Visa produkt',
|
||||
// Line 130: badge: {
|
||||
badge: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fresh: 'FÄRSK',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halal: 'HALAL',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 134: items: {
|
||||
items: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chickenWings: { name: 'Kycklingvingar färsk PL' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lambSteak: { name: 'Lammstek färsk Ireland' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beefMince: { name: 'Nötfärs 5% fett IRL' },
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 140: social: {
|
||||
social: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Följ oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Följ oss',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'1 100+ följare på Facebook · 249 inlägg · Dagliga uppdateringar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
facebook: 'Facebook',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
instagram: 'Instagram',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 149: contact: {
|
||||
contact: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'Kontakt',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Besök oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addressLabel: 'Adress',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phoneLabel: 'Telefon',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hoursLabel: 'Öppettider',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hoursValue: 'Alla dagar: {hours}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
writeUs: 'Skriv till oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
callUs: 'Ring oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
openMaps: 'Öppna i Google Maps',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
learnMore: 'Kontaktuppgifter',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 161: cta: {
|
||||
cta: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Redo för Premium Halal Kött?',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Beställ idag och upplev skillnaden med verkligt färskt, anpassat halal kött levererat till din dörr.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
button: 'Se vårt sortiment',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'Beställ på WhatsApp',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 168: shop: {
|
||||
shop: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Alla Produkter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'Premium halal kött, anpassat efter dina önskemål',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noProducts: 'Inga produkter hittades',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noProductsHint: 'Prova att justera dina filter eller sökfråga',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
filters: 'Filter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productsFound: '{count} produkter hittades',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
search: 'Sök',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
searchPlaceholder: 'Sök produkter...',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'Kategori',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortBy: 'Sortera efter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
all: 'Alla',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortFeatured: 'Utvalda',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortPriceAsc: 'Pris: Lägst till högst',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortPriceDesc: 'Pris: Högst till lägst',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortName: 'Namn A–Ö',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 185: product: {
|
||||
product: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
backToShop: 'Tillbaka till butiken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: 'I lager',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
outOfStock: 'Slut i lager',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalTrust: '100 % Halal-certifierad · Färskt dagligen · Premiumkvalitet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
yourSelection: 'Ditt val',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
aboutProduct: 'Om denna produkt',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addToCart: 'Lägg i varukorg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addedToCart: 'Tillagd i varukorg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
decreaseQty: 'Minska antal',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
increaseQty: 'Öka antal',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
removeWishlist: 'Ta bort från önskelista',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addWishlist: 'Lägg till i önskelista',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewProduct: 'Visa {name}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pieces: '{count} bitar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
standardCut: 'Standardstyckning',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
howManyCuts: 'Hur Många Styckningar Vill Du Ha?',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
howManyCutsHint: 'Välj antal styckningar för din beställning',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cutsAndStyle: '{cuts} styckningar · {style}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
selectCutting: 'Välj skärstil',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
selectCuttingHint: 'Våra slaktare förbereder ditt {category} exakt enligt din önskade styckning',
|
||||
// Line 206: fishNote:
|
||||
fishNote:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Fiskprodukter förbereds med vår standard professionella styckning — rengjord, fjällad och redo att tillagas.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 209: cutting: {
|
||||
cutting: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
nihari: 'Nihari-styckning',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
karahi: 'Karahi-styckning',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
qeema: 'Qeema (färs)',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
boneless: 'Benfri',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
steak: 'Biffstyckning',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 216: priceUnit: {
|
||||
priceUnit: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perBird: 'per kyckling',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perPack: 'per förpackning',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perKg: 'per kg',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 221: badges: {
|
||||
badges: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
bestseller: 'Bästsäljare',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chefsPick: 'Kockens val',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premium: 'Premium',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
popular: 'Populär',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshCatch: 'Färsk fångst',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 228: products: {
|
||||
products: {
|
||||
// Line 229: 'chicken-whole': {
|
||||
'chicken-whole': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Hel Kyckling',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Gårdsfärsk hel halal kyckling, perfekt för stekning eller curry.',
|
||||
// Line 232: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Våra hela kycklingar kommer från certifierade halalgårdar och levereras i toppskick. Varje fågel är handplockad för kvalitet, med mört kött och ren bearbetning. Välj önskat antal bitar så förbereder vi den precis som du behöver.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 235: 'chicken-breast': {
|
||||
'chicken-breast': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Kycklingbröst',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Magert, benfritt kycklingbröst — idealiskt för grillning och hälsosamma måltider.',
|
||||
// Line 238: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium benfritt kycklingbröst, trimmat och redo att tillagas. Perfekt för kebab, wok och hälsosamma vardagsmiddagar. Välj antal bitar och njut av konsekvent kvalitet varje gång.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 241: 'chicken-thighs': {
|
||||
'chicken-thighs': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Kycklinglår',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Saftiga halal kycklinglår med rik smak för curry och grill.',
|
||||
// Line 244: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Våra kycklinglår är kända för sin saftighet och djupa smak. Oavsett om du lagar traditionell karahi eller helg-BBQ levererar dessa lår varje gång.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 247: 'chicken-wings': {
|
||||
'chicken-wings': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Kycklingvingar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Festfärdiga halal kycklingvingar för stekning, bakning eller grillning.',
|
||||
// Line 250: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Krispiga, smakrika kycklingvingar tillagade enligt halal och levererade färskt. En favorit för matchkvällar och familjesammankomster.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 253: 'beef-nihari': {
|
||||
'beef-nihari': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Nötkött för Nihari',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Långkoksklara nötköttsstyckningar, perfekta för traditionell nihari.',
|
||||
// Line 256: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Särskilt utvalda nötköttsstyckningar idealiska för långkokt nihari. Rika på kollagen och smak, dessa styckningar bryts ner vackert under timmar av sjudning för en autentisk, smältande upplevelse.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 259: 'beef-steak': {
|
||||
'beef-steak': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Premium Nötbiff',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Restaurangkvalitet halal biffstyckningar för perfekt stekyta.',
|
||||
// Line 262: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Handskurna premium biffar från de finaste halal-källorna. Marmorering, mörhet och redo för din grill eller gjutjärnspanna. Välj önskad skärstil.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 265: 'beef-mince': {
|
||||
'beef-mince': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Nötfärs',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Färsk halal nötfärs för kebab, burgare och qeema.',
|
||||
// Line 268: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Finmalen halal nötfärs med perfekt fettförhållande för saftiga kebab, smakrik qeema och hemlagade burgare. Malas färskt dagligen.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 271: 'beef-boneless': {
|
||||
'beef-boneless': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Benfria Nötköttskuber',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Mångsidiga benfria nötköttskuber för karahi, biryani och grytor.',
|
||||
// Line 274: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Enhetliga benfria nötköttskuber skurna till perfektion för snabbkokta rätter. Idealiska för karahi, pulao och wok där konsekvent storlek är viktigt.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 277: 'lamb-shoulder': {
|
||||
'lamb-shoulder': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lammbog',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Rik, smakrik lammbog för långstekning och curry.',
|
||||
// Line 280: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium halal lammbog med vacker marmorering. Perfekt för långstekta festmåltider, rejäla curryrätter och traditionella familjemiddagar. Anpassad efter din önskade styckning.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 283: 'lamb-leg': {
|
||||
'lamb-leg': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lammlägg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Mört lammlägg för stekning, grillning och speciella tillfällen.',
|
||||
// Line 286: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Hela eller portionerade lammlägg från certifierade halal-källor. En centerstyckning för Eid-firanden, middagsbjudningar och söndagsstek.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 289: 'lamb-chops': {
|
||||
'lamb-chops': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lammkotletter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Premium lammkotletter för grillning och finmiddag hemma.',
|
||||
// Line 292: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Tjockskurna halal lammkotletter med perfekt fettlock för grillning. Restaurangkvalitet, levererad till ditt kök.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 295: 'lamb-mince': {
|
||||
'lamb-mince': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Lammfärs',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Färsk halal lammfärs för kebab, samosas och qeema.',
|
||||
// Line 298: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Finmalen lammfärs med rik smak. Nödvändig för seekh kebab, lamm qeema och fyllda parathas.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 301: 'fish-salmon': {
|
||||
'fish-salmon': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Atlantisk Laxfilé',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Smörig laxfilé, med skinn och redo att steka i panna.',
|
||||
// Line 304: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium atlantisk laxfilé med rikt omega-3-innehåll. Rengjord, portionerad och vakuumförpackad för maximal färskhet.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 307: 'fish-rohu': {
|
||||
'fish-rohu': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Rohu-fisk',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Hel rohu-fisk, rengjord och fjällad — en sydasiatisk favorit.',
|
||||
// Line 310: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Färsk rohu-fisk, en stapel i sydasiatisk matlagning. Rengjord, fjällad och urtagen. Perfekt för fiskcurry, stekt fisk och traditionella recept.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 313: 'fish-prawns': {
|
||||
'fish-prawns': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Jätteräkor',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Stora räkor med skal för grillning, curry och biryani.',
|
||||
// Line 316: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium jätteräkor, urtagna och redo att tillagas. Söt, fast kött som håller sig vackert i curry och tandoori-rätter.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 319: 'fish-basa': {
|
||||
'fish-basa': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Basa-filé',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'Mild, flagnig basa-filé — perfekt för nybörjare och barn.',
|
||||
// Line 322: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Benfria basa-filéer med mild, delikat smak. Lätt att tillaga och mångsidig — utmärkt för fisk-tacos, bakning och lätta curryrätter.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 326: cart: {
|
||||
cart: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Din Varukorg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
itemsCount: '{count} artikel/artiklar i din varukorg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
empty: 'Din varukorg är tom',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
emptyHint: 'Bläddra i vårt premium halal-sortiment och lägg till artiklar.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
startShopping: 'Börja handla',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization: 'Anpassning:',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderSummary: 'Ordersammanfattning',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtotal: 'Delsumma',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery: 'Leverans',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
free: 'Gratis',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freeDeliveryHint: 'Fri leverans på beställningar över 500 kr',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
total: 'Totalt',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
proceedCheckout: 'Gå till kassan',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
continueShopping: 'Fortsätt handla',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
removeItem: 'Ta bort artikel',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 343: checkout: {
|
||||
checkout: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Säker Kassa',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
backToCart: 'Tillbaka till varukorg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noItems: 'Inga artiklar att betala',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
goToShop: 'Gå till butiken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderConfirmed: 'Beställning bekräftad!',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
thankYou: 'Tack för din beställning. Ditt premium halal kött förbereds.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderId: 'Order-ID: {id}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewOrders: 'Visa beställningar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
haveAccount: 'Har du ett konto?',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signIn: 'Logga in',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fasterCheckout: 'för snabbare utcheckning.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryDetails: 'Leveransuppgifter',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fullName: 'Fullständigt namn',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'E-post',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: 'Telefon',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'Gatuadress',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'Stad',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'Län',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: 'Postnummer',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
payment: 'Betalning',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
creditCard: 'Kreditkort',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cashOnDelivery: 'Kontant vid leverans',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cardNumber: 'Kortnummer',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
expiry: 'Giltig till',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cvv: 'CVV',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
qty: 'Antal: {count}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
processing: 'Bearbetar...',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pay: 'Betala {amount}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
secure: 'Säker 256-bitars SSL-kryptering',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 374: auth: {
|
||||
auth: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
welcomeBack: 'Välkommen tillbaka',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createAccount: 'Skapa konto',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
joinTagline: 'Gå med i {name} för en premium shoppingupplevelse',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signInTagline: 'Logga in på ditt {name}-konto',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fullName: 'Fullständigt namn',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'E-post',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
password: 'Lösenord',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: 'Telefon',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'Gatuadress',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'Stad',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'Län',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: 'Postnummer',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signIn: 'Logga in',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
register: 'Skapa konto',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hasAccount: 'Har du redan ett konto? Logga in',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noAccount: 'Har du inget konto? Registrera dig',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
invalidCredentials: 'Ogiltig e-post eller lösenord. Prova {email} / demo123',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
demo: 'Demo: {email} / demo123',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 394: account: {
|
||||
account: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Mitt Konto',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
welcome: 'Välkommen tillbaka, {name}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
memberSince: 'Medlem sedan {date}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
myWishlist: 'Min önskelista',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signOut: 'Logga ut',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderHistory: 'Orderhistorik',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noOrders: 'Inga beställningar ännu',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
startShopping: 'Börja handla',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 404: wishlist: {
|
||||
wishlist: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Min Önskelista',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
saved: '{count} sparad(e) artikel/artiklar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
empty: 'Din önskelista är tom',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
emptyHint: 'Spara dina favoritprodukter för att köpa dem senare.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
browse: 'Bläddra produkter',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 411: about: {
|
||||
about: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'Om {name}',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Vi levererar premium 100 % halal kött till ditt bord — färskt, anpassat och med omsorg.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
ourStory: 'Vår Historia',
|
||||
// Line 416: storyP1:
|
||||
storyP1:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'{name} grundades med ett enkelt uppdrag: göra premium halal kött tillgängligt för varje familj, utan att kompromissa med kvalitet, färskhet eller religiös efterlevnad. Vi förstår att för många hushåll är rätt styckning tillagad på rätt sätt inte en lyx — det är nödvändigt.',
|
||||
// Line 418: storyP2:
|
||||
storyP2:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Från att välja antal bitar för kyckling till att välja Nihari- eller Karahi-styckning för nötkött och lamm, sätter vi anpassning i centrum för varje beställning. Våra expertslaktare förbereder varje order för hand, och vår temperaturkontrollerade leverans säkerställer att ditt kött anländer lika färskt som dagen det styckades.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalTitle: 'Halalcertifiering',
|
||||
// Line 421: halalDesc:
|
||||
halalDesc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Varje produkt hos {name} kommer från certifierade halal-leverantörer. Vår leveranskedja är fullt spårbar och vi upprätthåller strikt efterlevnad av halal-slakt och -bearbetningsstandarder. Vi arbetar uteslutande med gårdar och bearbetare som delar vårt engagemang för etiskt, religiöst korrekt köttproduktion.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDaily: 'Färskt Dagligen',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDailyDesc: 'Hämtas varje morgon från betrodda gårdar',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumQuality: 'Premiumkvalitet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumQualityDesc: 'Handplockade styckningar av expertslaktare',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fastDelivery: 'Snabb Leverans',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fastDeliveryDesc: 'Temperaturkontrollerad leverans samma dag',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryTitle: 'Leveransinformation',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery1: 'Vi levererar inom en radie på 40 km från vår anläggning.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery2: 'Beställningar före kl. 14 är berättigade till leverans samma dag.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery3: 'Fri leverans på beställningar över 500 kr. Standardleverans: 49 kr.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery4: 'Alla produkter vakuumförpackas och transporteras i isolerad förpackning.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contactTitle: 'Kontakta Oss',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
address: 'Tingvallavägen 11, 195 31 Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
privacyTitle: 'Integritetspolicy',
|
||||
// Line 437: privacyText:
|
||||
privacyText:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Vi samlar endast den information som behövs för att behandla dina beställningar — namn, kontaktuppgifter och leveransadress. Vi säljer inte dina uppgifter till tredje part. Betalningsuppgifter hanteras säkert av våra betalpartners.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
termsTitle: 'Användarvillkor',
|
||||
// Line 440: termsText:
|
||||
termsText:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Alla priser visas i SEK och kan ändras utan föregående meddelande. Beställningar är beroende av tillgång. Halalcertifiering gäller för alla köttprodukter. Leveranstider är uppskattningar och kan variera under högtrafik.',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 443: footer: {
|
||||
footer: {
|
||||
// Line 444: tagline:
|
||||
tagline:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Premium 100 % halal köttleverans. Färskt, anpassat kött levererat till din dörr med kompromisslös kvalitet.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'Butik',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
company: 'Företag',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contact: 'Kontakt',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
rights: 'Alla rättigheter förbehållna.',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: '072-585 50 50',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hours: 'Öppet alla dagar {hours}',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 453: notFound: {
|
||||
notFound: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: '404',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
message: 'Sidan hittades inte',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
goHome: 'Gå till startsidan',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 458: orderStatus: {
|
||||
orderStatus: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pending: 'väntande',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
confirmed: 'bekräftad',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
preparing: 'förbereds',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'out-for-delivery': 'ute för leverans',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivered: 'levererad',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
@@ -0,0 +1,936 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/i18n/locales/ur.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import external package or local module
|
||||
import { TranslationDict } from '../types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const ur: TranslationDict = {
|
||||
// Line 4: site: {
|
||||
site: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'کوٹ گارڈ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tagline: 'پریمیم حلال',
|
||||
// Line 7: description:
|
||||
description:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'پریمیم 100% حلال گوشت کی ڈیلیوری۔ تازہ اور منجمد مرغی، گائے کا گوشت، بکرے کا گوشت اور مچھلی — آپ کی پسند کے مطابق تیار کر کے آپ کے دروازے تک پہنچائی جاتی ہے۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
metaTitle: 'پریمیم حلال گوشت ڈیلیوری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
initials: 'KG',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'hello@kottgard.se',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
demoEmail: 'demo@kottgard.se',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 14: nav: {
|
||||
nav: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'خریداری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chicken: 'مرغی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beef: 'گائے کا گوشت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lamb: 'بکرے کا گوشت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fish: 'مچھلی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
about: 'ہمارے بارے میں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalCert: 'حلال سرٹیفیکیشن',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery: 'ڈیلیوری کی معلومات',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contact: 'رابطہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
myAccount: 'میرا اکاؤنٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderHistory: 'آرڈر کی تاریخ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
wishlist: 'پسندیدہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cart: 'ٹوکری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
privacy: 'رازداری کی پالیسی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
terms: 'شرائط و ضوابط',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
searchProducts: 'مصنوعات تلاش کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
toggleMenu: 'مینو کھولیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
account: 'اکاؤنٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
language: 'زبان',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 35: languageBanner: {
|
||||
languageBanner: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
choose: 'اپنی زبان منتخب کریں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 38: hero: {
|
||||
hero: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badge: '100% حلال تصدیق شدہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
taglineShort: 'قدرتی طور پر خالص',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'پریمیم حلال گوشت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
titleHighlight: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
titleEnd: '',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitleShort: 'تازہ۔ معیار۔ قابل اعتماد۔',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'حلال تصدیق شدہ · روزانہ تازہ · گھر کی ڈیلیوری · ہر دن کھلا',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hours: 'ہر دن کھلا {hours}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
location: 'Tingvallavägen 11, Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shopNow: 'ہمارا مجموعہ دیکھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
browseChicken: 'مرغی دیکھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'واٹس ایپ سے آرڈر کریں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 53: aboutPreview: {
|
||||
aboutPreview: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'ہمارے بارے میں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'مائرسٹا کی بہترین گوشت کی دکان',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p1: 'کوٹ گارڈ صرف ایک گوشت کی دکان نہیں — ہم معیار کا وعدہ ہیں۔ ہمارا تمام گوشت 100% حلال تصدیق شدہ ہے اور روزانہ تازہ پہنچایا جاتا ہے۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p2: 'ہم آئرلینڈ اور نیوزی لینڈ سے بکرے کا گوشت، معتبر پیدا کنندگان سے مرغی اور گائے کا گوشت حاصل کرتے ہیں، اور آپ کو رات کے کھانے، تقریب یا اتوار کی روست کے لیے صحیح کٹ تلاش کرنے میں مدد کرتے ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
p3: 'Tingvallavägen پر ہماری دکان میں آئیں، بتائیں آپ کیا تلاش کر رہے ہیں — ہم آپ کی خواہش کے مطابق کاٹتے اور پیک کرتے ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statHalal: '100%',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statHalalLabel: 'حلال تصدیق شدہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDays: '7 دن',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDaysLabel: 'ہفتے میں کھلا',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDelivery: 'روزانہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statDeliveryLabel: 'ڈیلیوری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statFresh: 'تازہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
statFreshLabel: 'ہر دن',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
readMore: 'مزید پڑھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
imageAlt: 'کوٹ گارڈ سے تازہ گوشت کی کٹس',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 70: trust: {
|
||||
trust: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halal: '100% حلال',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalDesc: 'مکمل سراغ رسانی اور تعمیل کے ساتھ تصدیق شدہ حلال ذرائع۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fresh: 'روزانہ تازہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDesc: 'ہر صبح تازہ حاصل کیا جاتا ہے اور بہترین حالت میں پہنچایا جاتا ہے۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premium: 'پریمیم معیار',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumDesc: 'قابل اعتماد فارموں سے منتخب کردہ کٹس، ماہر قصابوں کے ذریعے تیار۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 78: categories: {
|
||||
categories: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'زمرے کے لحاظ سے خریداری',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'روزمرہ کی مرغی سے لے کر پریمیم بکرے کے کٹlets تک — ہر کٹ آپ کی پسند کے مطابق۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: '{name} خریدیں',
|
||||
// Line 83: chicken: {
|
||||
chicken: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'مرغی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'فارم سے تازہ حلال مرغی، آپ کی پسند کے مطابق کاٹی گئی',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 87: beef: {
|
||||
beef: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'گائے کا گوشت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'روایتی اور جدید کٹس میں پریمیم حلال گائے کا گوشت',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 91: lamb: {
|
||||
lamb: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'بکرے کا گوشت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'ہر موقع کے لیے نرم حلال بکرے کا گوشت',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 95: fish: {
|
||||
fish: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'مچھلی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'تازہ پکڑ، صاف کی گئی اور پکانے کے لیے تیار',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 100: featured: {
|
||||
featured: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'منتخب مجموعہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'نمایاں مصنوعات',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'ہماری سب سے مقبول کٹس، شہر بھر کے خاندانوں کی پسندیدہ۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewAll: 'سب دیکھیں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 106: howItWorks: {
|
||||
howItWorks: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'آرڈر',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'آرڈر کرنا کتنا آسان ہے',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step1Title: 'ہم سے رابطہ کریں',
|
||||
// Line 110: step1Desc:
|
||||
step1Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'واٹس ایپ پر پیغام بھیجیں کہ آپ کیا چاہتے ہیں — ہم جلدی جواب دیتے ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step2Title: 'ہم تصدیق کرتے ہیں',
|
||||
// Line 113: step2Desc:
|
||||
step2Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'ہم آپ کے آرڈر کی تصدیق کرتے ہیں، قیمت بتاتے ہیں اور بتاتے ہیں کہ کب تیار ہوگا۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step3Title: 'وصول یا ڈیلیوری',
|
||||
// Line 116: step3Desc:
|
||||
step3Desc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'Tingvallavägen 11 پر دکان سے وصول کریں یا گھر کی ڈیلیوری منتخب کریں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
step: 'مرحلہ {n}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'واٹس ایپ پر آرڈر کریں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 121: offers: {
|
||||
offers: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'آرڈر',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'ہفتہ وار پیشکشیں',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'ہم مسلسل تازہ پیشکشوں کے ساتھ اپ ڈیٹ کرتے ہیں۔ تازہ ترین قیمتوں کے لیے سوشل میڈیا پر فالو کریں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
disclaimer: 'قیمت اس وقت تک جب تک اسٹاک موجود ہے',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
was: 'پہلے',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
now: 'اب',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
order: 'آرڈر',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewProduct: 'مصنوعات دیکھیں',
|
||||
// Line 131: badge: {
|
||||
badge: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fresh: 'تازہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halal: 'حلال',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 135: items: {
|
||||
items: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chickenWings: { name: 'تازہ چکن ونگز PL' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lambSteak: { name: 'تازہ لیمب روست آئرلینڈ' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beefMince: { name: 'بیف منس 5% چکنائی IRL' },
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 141: social: {
|
||||
social: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'ہمیں فالو کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'ہمیں فالو کریں',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'فیس بک پر 1,100+ فالوورز · 249 پوسٹس · روزانہ اپ ڈیٹس',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
facebook: 'فیس بک',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
instagram: 'انسٹاگرام',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'واٹس ایپ',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 150: contact: {
|
||||
contact: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
label: 'رابطہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'ہم سے ملیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addressLabel: 'پتہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phoneLabel: 'فون',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hoursLabel: 'اوقات',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hoursValue: 'ہر دن: {hours}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
writeUs: 'ہمیں لکھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
callUs: 'ہمیں کال کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
openMaps: 'گوگل میپس میں کھولیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
learnMore: 'رابطے کی تفصیلات',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 162: cta: {
|
||||
cta: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'پریمیم حلال گوشت کے لیے تیار ہیں؟',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'آج ہی آرڈر کریں اور حقیقی تازہ، حسب ضرورت حلال گوشت کا فرق محسوس کریں جو آپ کے دروازے تک پہنچایا جائے۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
button: 'ہمارا مجموعہ دیکھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsapp: 'واٹس ایپ پر آرڈر کریں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 169: shop: {
|
||||
shop: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'تمام مصنوعات',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtitle: 'پریمیم حلال گوشت، آپ کی پسند کے مطابق',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noProducts: 'کوئی مصنوعات نہیں ملیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noProductsHint: 'اپنے فلٹرز یا تلاش کی کوشش کو ایڈجسٹ کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
filters: 'فلٹرز',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productsFound: '{count} مصنوعات ملیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
search: 'تلاش',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
searchPlaceholder: 'مصنوعات تلاش کریں...',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'زمرہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortBy: 'ترتیب دیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
all: 'سب',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortFeatured: 'نمایاں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortPriceAsc: 'قیمت: کم سے زیادہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortPriceDesc: 'قیمت: زیادہ سے کم',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sortName: 'نام الف سے ی',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 186: product: {
|
||||
product: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
backToShop: 'خریداری پر واپس',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: 'دستیاب',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
outOfStock: 'اسٹاک ختم',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalTrust: '100% حلال تصدیق شدہ · روزانہ تازہ · پریمیم معیار',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
yourSelection: 'آپ کا انتخاب',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
aboutProduct: 'اس مصنوع کے بارے میں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addToCart: 'ٹوکری میں شامل کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addedToCart: 'ٹوکری میں شامل ہو گیا',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
decreaseQty: 'مقدار کم کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
increaseQty: 'مقدار بڑھائیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
removeWishlist: 'پسندیدہ سے ہٹائیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
addWishlist: 'پسندیدہ میں شامل کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewProduct: '{name} دیکھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pieces: '{count} ٹکڑے',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
standardCut: 'معیاری کٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
howManyCuts: 'آپ کتنے کٹ چاہتے ہیں؟',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
howManyCutsHint: 'اپنے آرڈر کے لیے کٹوں کی تعداد منتخب کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cutsAndStyle: '{cuts} کٹ · {style}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
selectCutting: 'کاٹنے کا انداز منتخب کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
selectCuttingHint: 'ہمارے قصاب آپ کے {category} کو بالکل آپ کی پسند کے مطابق تیار کریں گے',
|
||||
// Line 207: fishNote:
|
||||
fishNote:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'مچھلی کی مصنوعات ہمارے معیاری پیشہ ورانہ کٹ سے تیار کی جاتی ہیں — صاف، چھلکے اتارے اور پکانے کے لیے تیار۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 210: cutting: {
|
||||
cutting: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
nihari: 'نہاری کٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
karahi: 'کڑاہی کٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
qeema: 'قیمہ (کима)',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
boneless: 'بغیر ہڈی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
steak: 'اسٹیک کٹ',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 217: priceUnit: {
|
||||
priceUnit: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perBird: 'فی مرغی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perPack: 'فی پیک',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
perKg: 'فی کلو',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 222: badges: {
|
||||
badges: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
bestseller: 'سب سے زیادہ فروخت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chefsPick: 'شیف کی پسند',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premium: 'پریمیم',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
popular: 'مقبول',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshCatch: 'تازہ پکڑ',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 229: products: {
|
||||
products: {
|
||||
// Line 230: 'chicken-whole': {
|
||||
'chicken-whole': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'پوری مرغی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'فارم سے تازہ پوری حلال مرغی، بھوننے یا کڑی کے لیے بہترین۔',
|
||||
// Line 233: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'ہماری پوری مرغیاں تصدیق شدہ حلال فارموں سے حاصل کی جاتی ہیں اور بہترین تازگی میں پہنچائی جاتی ہیں۔ ہر پرندہ معیار کے لیے ہاتھ سے منتخب کیا جاتا ہے، نرم گوشت اور صاف پروسیسنگ کے ساتھ۔ اپنی پسندیدہ ٹکڑوں کی تعداد منتخب کریں اور ہم اسے بالکل ویسے تیار کریں گے جیسا آپ چاہیں۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 236: 'chicken-breast': {
|
||||
'chicken-breast': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'مرغی کا سینہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'دبلا، بغیر ہڈی کا مرغی کا سینہ — گرل اور صحت مند کھانوں کے لیے بہترین۔',
|
||||
// Line 239: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'پریمیم بغیر ہڈی کا مرغی کا سینہ، تراشا ہوا اور پکانے کے لیے تیار۔ کباب، سٹیر فرائی اور صحت مند ہفتے کی رات کے کھانوں کے لیے بہترین۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 242: 'chicken-thighs': {
|
||||
'chicken-thighs': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'مرغی کی ران',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'کڑی اور گرل کے لیے بھرپور ذائقے والی رس بھرے حلال مرغی کی ران۔',
|
||||
// Line 245: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'ہماری مرغی کی ران اپنی رس اور گہرے ذائقے کے لیے مشہور ہے۔ چاہے روایتی کڑاہی بنائیں یا ہفتے کے آخر میں BBQ، یہ ران ہر بار بہترین نتائج دیتی ہے۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 248: 'chicken-wings': {
|
||||
'chicken-wings': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'مرغی کے بازو',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'تلی، بیکنگ یا گرل کے لیے پارٹی کے لیے تیار حلال مرغی کے بازو۔',
|
||||
// Line 251: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'کرکرے، ذائقے دار مرغی کے بازو حلال طریقے سے تیار اور تازہ پہنچائے جاتے ہیں۔ میچ کی راتوں اور خاندانی محفلوں کی پسندیدہ۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 254: 'beef-nihari': {
|
||||
'beef-nihari': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'نہاری کے لیے گائے کا گوشت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'آہستہ پکانے کے لیے تیار گائے کے گوشت کے کٹس، روایتی نہاری کے لیے بہترین۔',
|
||||
// Line 257: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'خاص طور پر منتخب گائے کے گوشت کے کٹس جو آہستہ پکی ہوئی نہاری کے لیے بہترین ہیں۔ کولیجن اور ذائقے سے بھرپور، یہ کٹس گھنٹوں کی دھیمی آنچ پر خوبصورتی سے گل جاتے ہیں۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 260: 'beef-steak': {
|
||||
'beef-steak': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'پریمیم گائے کا اسٹیک',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'ریستوران معیار کے حلال اسٹیک کٹس، بہترین سیک کے لیے۔',
|
||||
// Line 263: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'بہترین حلال ذرائع سے ہاتھ سے کاٹے گئے پریمیم اسٹیک۔ چربیلے، نرم اور آپ کی گرل یا کاسٹ آئرن پین کے لیے تیار۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 266: 'beef-mince': {
|
||||
'beef-mince': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'گائے کا قیمہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'کباب، برگر اور قیمہ کے لیے تازہ حلال گائے کا قیمہ۔',
|
||||
// Line 269: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'باریک پیسا ہوا حلال گائے کا قیمہ بہترین چربی کے تناسب کے ساتھ رسیلے کباب، ذائقے دار قیمہ اور گھریلو برگرز کے لیے۔ روزانہ تازہ پیسا جاتا ہے۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 272: 'beef-boneless': {
|
||||
'beef-boneless': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'بغیر ہڈی کے گائے کے مکعب',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'کڑاہی، بریانی اور اسٹیو کے لیے کثیر الاستعمال بغیر ہڈی کے مکعب۔',
|
||||
// Line 275: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'یکساں بغیر ہڈی کے گائے کے مکعب تیز پکوان کے لیے کامل سائز میں کاٹے گئے۔ کڑاہی، پلاؤ اور سٹیر فرائی کے لیے بہترین۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 278: 'lamb-shoulder': {
|
||||
'lamb-shoulder': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'بکرے کا کندھا',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'آہستہ بھوننے اور کڑی کے لیے بھرپور ذائقے والا بکرے کا کندھا۔',
|
||||
// Line 281: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'خوبصورت چربی کے نمونے والے پریمیم حلال بکرے کا کندھا۔ آہستہ بھونی ہوئی دعوتوں، بھرپور کڑیوں اور روایتی خاندانی کھانوں کے لیے بہترین۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 284: 'lamb-leg': {
|
||||
'lamb-leg': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'بکرے کی ٹانگ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'بھوننے، گرل اور خاص مواقع کے لیے نرم بکرے کی ٹانگ۔',
|
||||
// Line 287: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'تصدیق شدہ حلال ذرائع سے پوری یا حصوں میں بکرے کی ٹانگ۔ عید کی تقریبات، ڈنر پارٹیوں اور اتوار کی بھوننے کے لیے مرکزی کٹ۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 290: 'lamb-chops': {
|
||||
'lamb-chops': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'بکرے کے کٹlets',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'گرل اور گھر میں فائن ڈائننگ کے لیے پریمیم بکرے کے کٹlets۔',
|
||||
// Line 293: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'موٹے کاٹے گئے حلال بکرے کے کٹlets گرل کے لیے بہترین چربی کی تہہ کے ساتھ۔ ریستوران معیار، آپ کے باورچی خانے تک پہنچایا گیا۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 296: 'lamb-mince': {
|
||||
'lamb-mince': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'بکرے کا قیمہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'کباب، سموسے اور قیمہ کے لیے تازہ حلال بکرے کا قیمہ۔',
|
||||
// Line 299: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'باریک پیسا ہوا بکرے کا قیمہ بھرپور ذائقے کے ساتھ۔ سیخ کباب، بکرے کا قیمہ اور بھرے ہوئے پراٹھوں کے لیے ضروری۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 302: 'fish-salmon': {
|
||||
'fish-salmon': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'اٹلانٹک سامن فلیٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'مکھن جیسے نرم سامن فلیٹس، جلد سمیت اور پین میں سیکنے کے لیے تیار۔',
|
||||
// Line 305: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'پریمیم اٹلانٹک سامن فلیٹس بھرپور اومیگا 3 کے ساتھ۔ صاف، حصوں میں تقسیم اور زیادہ سے زیادہ تازگی کے لیے ویکیوم سیلڈ۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 308: 'fish-rohu': {
|
||||
'fish-rohu': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'روہو مچھلی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'پوری روہو مچھلی، صاف اور چھلکے اتارے — جنوبی ایشیائی پسندیدہ۔',
|
||||
// Line 311: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'تازہ روہو مچھلی، جنوبی ایشیائی کھانوں کا ایک بنیادی جزو۔ صاف، چھلکے اتارے اور اندر صاف کی گئی۔ مچھلی کی کڑی، تلی ہوئی مچھلی اور روایتی ترکیبوں کے لیے بہترین۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 314: 'fish-prawns': {
|
||||
'fish-prawns': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'بڑی جھینگے',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'گرل، کڑی اور بریانی کے لیے بڑے چھلکے سمیت جھینگے۔',
|
||||
// Line 317: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'پریمیم بڑی جھینگے، صاف کی گئی اور پکانے کے لیے تیار۔ میٹھا، مضبوط گوشت جو کڑی اور تندوری تیاریوں میں خوبصورتی سے کھڑا رہتا ہے۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 320: 'fish-basa': {
|
||||
'fish-basa': {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'باسا فلیٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: 'ہلکا، پرت دار باسا فلیٹس — ابتدائیوں اور بچوں کے لیے بہترین۔',
|
||||
// Line 323: longDescription:
|
||||
longDescription:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'بغیر ہڈی کے باسا فلیٹس ہلکے، نفیس ذائقے کے ساتھ۔ پکانا آسان اور کثیر الاستعمال — مچھلی کے ٹیکو، بیکنگ اور ہلکی کڑیوں کے لیے بہترین۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 327: cart: {
|
||||
cart: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'آپ کی ٹوکری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
itemsCount: 'آپ کی ٹوکری میں {count} آئٹم',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
empty: 'آپ کی ٹوکری خالی ہے',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
emptyHint: 'ہمارے پریمیم حلال مجموعے میں سے دیکھیں اور آئٹمز شامل کریں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
startShopping: 'خریداری شروع کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization: 'حسب ضرورت:',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderSummary: 'آرڈر کا خلاصہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
subtotal: 'ذیلی کل',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery: 'ڈیلیوری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
free: 'مفت',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freeDeliveryHint: '500 کرون سے زیادہ کے آرڈرز پر مفت ڈیلیوری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
total: 'کل',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
proceedCheckout: 'چیک آؤٹ پر جائیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
continueShopping: 'خریداری جاری رکھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
removeItem: 'آئٹم ہٹائیں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 344: checkout: {
|
||||
checkout: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'محفوظ چیک آؤٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
backToCart: 'ٹوکری پر واپس',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noItems: 'چیک آؤٹ کے لیے کوئی آئٹم نہیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
goToShop: 'خریداری پر جائیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderConfirmed: 'آرڈر کی تصدیق ہو گئی!',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
thankYou: 'آپ کے آرڈر کا شکریہ۔ آپ کا پریمیم حلال گوشت تیار کیا جا رہا ہے۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderId: 'آرڈر آئی ڈی: {id}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
viewOrders: 'آرڈرز دیکھیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
haveAccount: 'اکاؤنٹ ہے؟',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signIn: 'سائن ان کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fasterCheckout: 'تیز چیک آؤٹ کے لیے۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryDetails: 'ڈیلیوری کی تفصیلات',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fullName: 'پورا نام',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'ای میل',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: 'فون',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'گلی کا پتہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'شہر',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'صوبہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: 'پوسٹل کوڈ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
payment: 'ادائیگی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
creditCard: 'کریڈٹ کارڈ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cashOnDelivery: 'ڈیلیوری پر نقد',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cardNumber: 'کارڈ نمبر',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
expiry: 'میعاد',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cvv: 'CVV',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
qty: 'مقدار: {count}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
processing: 'پروسیسنگ...',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pay: '{amount} ادا کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
secure: 'محفوظ 256 بٹ SSL انکرپشن',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 375: auth: {
|
||||
auth: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
welcomeBack: 'خوش آمدید',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createAccount: 'اکاؤنٹ بنائیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
joinTagline: 'پریمیم شاپنگ کے تجربے کے لیے {name} میں شامل ہوں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signInTagline: 'اپنے {name} اکاؤنٹ میں سائن ان کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fullName: 'پورا نام',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: 'ای میل',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
password: 'پاس ورڈ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: 'فون',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'گلی کا پتہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'شہر',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'صوبہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: 'پوسٹل کوڈ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signIn: 'سائن ان',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
register: 'اکاؤنٹ بنائیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hasAccount: 'پہلے سے اکاؤنٹ ہے؟ سائن ان کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noAccount: 'اکاؤنٹ نہیں ہے؟ رجسٹر کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
invalidCredentials: 'غلط ای میل یا پاس ورڈ۔ {email} / demo123 آزمائیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
demo: 'ڈیمو: {email} / demo123',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 395: account: {
|
||||
account: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'میرا اکاؤنٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
welcome: 'خوش آمدید، {name}',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
memberSince: '{date} سے رکن',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
myWishlist: 'میری پسندیدہ فہرست',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
signOut: 'سائن آؤٹ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderHistory: 'آرڈر کی تاریخ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
noOrders: 'ابھی تک کوئی آرڈر نہیں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
startShopping: 'خریداری شروع کریں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 405: wishlist: {
|
||||
wishlist: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: 'میری پسندیدہ فہرست',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
saved: '{count} محفوظ آئٹم',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
empty: 'آپ کی پسندیدہ فہرست خالی ہے',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
emptyHint: 'اپنی پسندیدہ مصنوعات محفوظ کریں تاکہ بعد میں خرید سکیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
browse: 'مصنوعات دیکھیں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 412: about: {
|
||||
about: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: '{name} کے بارے میں',
|
||||
// Translated UI string for current language (sv / en / ur)
|
||||
subtitle:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'پریمیم 100% حلال گوشت آپ کی میز تک — تازہ، حسب ضرورت اور احتیاط سے پہنچایا گیا۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
ourStory: 'ہماری کہانی',
|
||||
// Line 417: storyP1:
|
||||
storyP1:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'{name} ایک سادہ مشن کے ساتھ قائم ہوا: ہر خاندان کے لیے پریمیم حلال گوشت قابل رسائی بنانا، معیار، تازگی یا مذہبی تعمیل سے سمجھوتہ کیے بغیر۔ ہم سمجھتے ہیں کہ بہت سے گھروں کے لیے صحیح کٹ صحیح طریقے سے تیار کرنا عیش نہیں — یہ ضروری ہے۔',
|
||||
// Line 419: storyP2:
|
||||
storyP2:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'مرغی کے لیے ٹکڑوں کی تعداد منتخب کرنے سے لے کر گائے اور بکرے کے گوشت کے لیے نہاری یا کڑاہی کٹس کا انتخاب کرنے تک، ہم ہر آرڈر کے مرکز میں حسب ضرورت بناتے ہیں۔ ہمارے ماہر قصاب ہر آرڈر ہاتھ سے تیار کرتے ہیں، اور ہماری درجہ حرارت کنٹرولڈ ڈیلیوری یقینی بناتی ہے کہ آپ کا گوشت اسی دن کی طرح تازہ پہنچے جیسے کاٹا گیا تھا۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
halalTitle: 'حلال سرٹیفیکیشن',
|
||||
// Line 422: halalDesc:
|
||||
halalDesc:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'{name} کی ہر مصنوع تصدیق شدہ حلال سپلائرز سے حاصل کی جاتی ہے۔ ہمارا سپلائی چین مکمل طور پر قابل سراغ ہے، اور ہم حلال ذبح اور پروسیسنگ کے معیارات کی سخت تعمیل برقرار رکھتے ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDaily: 'روزانہ تازہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
freshDailyDesc: 'ہر صبح قابل اعتماد فارموں سے حاصل',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumQuality: 'پریمیم معیار',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
premiumQualityDesc: 'ماہر قصابوں کے ذریعے منتخب کردہ کٹس',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fastDelivery: 'تیز ڈیلیوری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fastDeliveryDesc: 'درجہ حرارت کنٹرولڈ اسی دن کی ڈیلیوری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryTitle: 'ڈیلیوری کی معلومات',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery1: 'ہم اپنی پروسیسنگ سہولت سے 40 کلومیٹر کے دائرے میں ڈیلیوری کرتے ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery2: 'دوپہر 2 بجے سے پہلے کے آرڈرز اسی دن کی ڈیلیوری کے اہل ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery3: '500 کرون سے زیادہ کے آرڈرز پر مفت ڈیلیوری۔ معیاری ڈیلیوری فیس: 49 کرون۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivery4: 'تمام مصنوعات ویکیوم سیلڈ اور انسولیٹڈ پیکجنگ میں منتقل کی جاتی ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contactTitle: 'ہم سے رابطہ کریں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
address: 'Tingvallavägen 11, 195 31 Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
privacyTitle: 'رازداری کی پالیسی',
|
||||
// Line 438: privacyText:
|
||||
privacyText:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'ہم صرف وہ معلومات جمع کرتے ہیں جو آپ کے آرڈرز پر کارروائی کے لیے ضروری ہیں — نام، رابطے کی تفصیلات اور ڈیلیوری کا پتہ۔ ہم آپ کا ڈیٹا تیسرے فریق کو نہیں بیچتے۔ ادائیگی کی تفصیلات محفوظ طریقے سے ہمارے پارٹنرز کے ذریعے سنبھالی جاتی ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
termsTitle: 'سروس کی شرائط',
|
||||
// Line 441: termsText:
|
||||
termsText:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'تمام قیمتیں SEK میں دکھائی جاتی ہیں اور بغیر اطلاع کے تبدیل ہو سکتی ہیں۔ آرڈرز دستیابی پر منحصر ہیں۔ تمام گوشت کی مصنوعات پر حلال سرٹیفیکیشن لاگو ہوتا ہے۔ ڈیلیوری کے اوقات تخمینے ہیں اور مصروف اوقات میں مختلف ہو سکتے ہیں۔',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 444: footer: {
|
||||
footer: {
|
||||
// Line 445: tagline:
|
||||
tagline:
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'پریمیم 100% حلال گوشت ڈیلیوری۔ تازہ، حسب ضرورت کٹس آپ کے دروازے تک بغیر کسی سمجھوتے کے معیار کے ساتھ پہنچائی جاتی ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
shop: 'خریداری',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
company: 'کمپنی',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
contact: 'رابطہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
rights: 'جملہ حقوق محفوظ ہیں۔',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: '072-585 50 50',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hours: 'ہر دن کھلا {hours}',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 454: notFound: {
|
||||
notFound: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
title: '404',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
message: 'صفحہ نہیں ملا',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
goHome: 'ہوم پر جائیں',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 459: orderStatus: {
|
||||
orderStatus: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
pending: 'زیر التوا',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
confirmed: 'تصدیق شدہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
preparing: 'تیاری میں',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'out-for-delivery': 'ڈیلیوری کے لیے روانہ',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
delivered: 'پہنچا دیا گیا',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/i18n/types.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type Locale = 'en' | 'sv' | 'ur';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const LOCALES: { code: Locale; label: string; nativeLabel: string }[] = [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ code: 'sv', label: 'Swedish', nativeLabel: 'Svenska' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ code: 'en', label: 'English', nativeLabel: 'English' },
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
{ code: 'ur', label: 'Urdu', nativeLabel: 'اردو' },
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const DEFAULT_LOCALE: Locale = 'sv';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface TranslationDict {
|
||||
// Line 12: [key: string]: string | TranslationDict;
|
||||
[key: string]: string | TranslationDict;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/constants.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { CutCount, CuttingStyleKey } from '@/types';
|
||||
// Import from a relative file in the same project
|
||||
import { IMAGES } from './images';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const CUT_COUNTS: CutCount[] = [4, 8, 10, 12];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const CUTTING_STYLE_KEYS: CuttingStyleKey[] = [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'nihari',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'karahi',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'qeema',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'boneless',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'steak',
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const MEAT_CATEGORIES = ['chicken', 'beef', 'lamb'] as const;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const CATEGORY_IDS = ['chicken', 'beef', 'lamb', 'fish'] as const;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const CATEGORY_IMAGES = IMAGES.categories;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const SITE_NAME = 'Kött Gård';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const SITE_EMAIL = 'hello@kottgard.se';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const DEMO_EMAIL = 'demo@kottgard.se';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const SITE_PHONE = '+46 72 585 50 50';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const SITE_PHONE_DISPLAY = '072-585 50 50';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const SITE_ADDRESS = 'Tingvallavägen 11, 195 31 Märsta';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const SITE_LOCATION = 'Märsta · Sverige';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const SITE_HOURS = '10:00–19:00';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const FACEBOOK_URL = 'https://www.facebook.com/kottgard/';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const INSTAGRAM_URL = 'https://www.instagram.com/kottgard';
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const MAPS_URL = 'https://share.google/fUkkhDNlhDTcImKo5';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 32: const WHATSAPP_BASE = 'Hej Kött Gård! Jag vill beställa';
|
||||
const WHATSAPP_BASE = 'Hej Kött Gård! Jag vill beställa';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function whatsappOrderUrl(product?: string): string {
|
||||
// Line 35: const text = product ? `${WHATSAPP_BASE} ${product}.` : `...
|
||||
const text = product ? `${WHATSAPP_BASE} ${product}.` : `${WHATSAPP_BASE}.`;
|
||||
// Return value from function
|
||||
return `https://wa.me/46725855050?text=${encodeURIComponent(text)}`;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const WHATSAPP_URL = whatsappOrderUrl();
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/customization.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Category, MeatCustomization, ProductCustomization } from '@/types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript type alias — union or shorthand for complex types
|
||||
type Translator = (path: string, params?: Record<string, string | number>) => string;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getDefaultCustomization(category: Category): ProductCustomization {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (category === 'fish') {
|
||||
// Return value from function
|
||||
return { type: 'fish' };
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Return value from function
|
||||
return {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
type: category,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cuts: 8,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cuttingStyle: 'karahi',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getCustomizationKey(customization: ProductCustomization): string {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (customization.type === 'fish') {
|
||||
// Return value from function
|
||||
return 'standard';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Return value from function
|
||||
return `cuts-${customization.cuts}::${customization.cuttingStyle}`;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getCustomizationLabel(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization: ProductCustomization,
|
||||
// Line 25: t: Translator
|
||||
t: Translator
|
||||
// Line 26: ): string {
|
||||
): string {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (customization.type === 'fish') {
|
||||
// Return value from function
|
||||
return t('product.standardCut');
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Return value from function
|
||||
return t('product.cutsAndStyle', {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cuts: customization.cuts,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
style: t(`cutting.${customization.cuttingStyle}`),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getCartItemKey(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productId: string,
|
||||
// Line 38: customization: ProductCustomization
|
||||
customization: ProductCustomization
|
||||
// Line 39: ): string {
|
||||
): string {
|
||||
// Return value from function
|
||||
return `${productId}::${getCustomizationKey(customization)}`;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function updateMeatCustomization(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
current: ProductCustomization,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: Category,
|
||||
// Line 46: update: Partial<Pick<MeatCustomization, 'cuts' | 'cutting...
|
||||
update: Partial<Pick<MeatCustomization, 'cuts' | 'cuttingStyle'>>
|
||||
// Line 47: ): MeatCustomization {
|
||||
): MeatCustomization {
|
||||
// Line 48: const base =
|
||||
const base =
|
||||
// Line 49: current.type !== 'fish' && current.type === category
|
||||
current.type !== 'fish' && current.type === category
|
||||
// Line 50: ? current
|
||||
? current
|
||||
// Line 51: : (getDefaultCustomization(category) as MeatCustomization);
|
||||
: (getDefaultCustomization(category) as MeatCustomization);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
type: category as MeatCustomization['type'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cuts: update.cuts ?? base.cuts,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
cuttingStyle: update.cuttingStyle ?? base.cuttingStyle,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/demo-orders.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Order } from '@/types';
|
||||
// Import from a relative file in the same project
|
||||
import { products } from './products';
|
||||
// Import from a relative file in the same project
|
||||
import { getDefaultCustomization, getCartItemKey } from './customization';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Function declaration — reusable logic in this file
|
||||
function orderItem(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productId: string,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
quantity: number,
|
||||
// Line 8: customizationLabel: string
|
||||
customizationLabel: string
|
||||
// Line 9: ) {
|
||||
) {
|
||||
// Array.find — get first matching item or undefined
|
||||
const product = products.find((p) => p.id === productId)!;
|
||||
// Line 11: const customization = getDefaultCustomization(product.cat...
|
||||
const customization = getDefaultCustomization(product.category);
|
||||
// Return value from function
|
||||
return {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: getCartItemKey(productId, customization),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
product,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
quantity,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customizationLabel,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 21: const demoAddress = {
|
||||
const demoAddress = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'Tingvallavägen 11',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'Stockholm',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: '195 31',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const DEMO_ORDERS: Order[] = [
|
||||
// Line 29: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'KG-2026-0042',
|
||||
// Line 31: items: [
|
||||
items: [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderItem('beef-steak', 2, '8 cuts · Karahi'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderItem('lamb-shoulder', 1, '8 cuts · Nihari'),
|
||||
// End of array literal
|
||||
],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
total: 647,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
status: 'delivered',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createdAt: '2026-06-10T14:30:00.000Z',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryAddress: demoAddress,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
paymentMethod: 'card',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 41: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'KG-2026-0051',
|
||||
// Line 43: items: [
|
||||
items: [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderItem('chicken-whole', 2, '8 cuts · Karahi'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderItem('beef-mince', 1, '8 cuts · Qeema'),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orderItem('fish-prawns', 1, 'Standard cut'),
|
||||
// End of array literal
|
||||
],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
total: 556,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
status: 'out-for-delivery',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createdAt: '2026-06-16T09:15:00.000Z',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryAddress: demoAddress,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
paymentMethod: 'swish',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 54: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'KG-2026-0058',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
items: [orderItem('lamb-leg', 1, '8 cuts · Steak'), orderItem('fish-salmon', 1, 'Standard cut')],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
total: 418,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
status: 'preparing',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createdAt: '2026-06-17T08:00:00.000Z',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
deliveryAddress: demoAddress,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
paymentMethod: 'card',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// End of array literal
|
||||
];
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/images.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Block comment — documents the file or function below
|
||||
/**
|
||||
// Block comment — documents the file or function below
|
||||
* All imagery: raw, fresh, uncooked meat & seafood only.
|
||||
// Block comment — documents the file or function below
|
||||
* High-quality Unsplash photos (verified URLs).
|
||||
// Block comment — documents the file or function below
|
||||
*/
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript type alias — union or shorthand for complex types
|
||||
type UnsplashOptions = {
|
||||
// Line 7: height?: number;
|
||||
height?: number;
|
||||
// Line 8: crop?: 'center' | 'top' | 'bottom' | 'entropy';
|
||||
crop?: 'center' | 'top' | 'bottom' | 'entropy';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function unsplashUrl(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
path: string,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
width: number,
|
||||
// Line 14: options?: UnsplashOptions
|
||||
options?: UnsplashOptions
|
||||
// Line 15: ): string {
|
||||
): string {
|
||||
// Line 16: const id = path.startsWith('photo-') ? path : `photo-${pa...
|
||||
const id = path.startsWith('photo-') ? path : `photo-${path}`;
|
||||
// Line 17: const params = new URLSearchParams({
|
||||
const params = new URLSearchParams({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
auto: 'format',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fit: 'crop',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
w: String(width),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
q: '90',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (options?.height) params.set('h', String(options.height));
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (options?.crop) params.set('crop', options.crop);
|
||||
// Return value from function
|
||||
return `https://images.unsplash.com/${id}?${params.toString()}`;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Block comment — documents the file or function below
|
||||
/** Verified raw-meat & seafood Unsplash IDs */
|
||||
// Line 29: const RAW = {
|
||||
const RAW = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
butcherCounter: 'photo-1607623814075-e51df1bdc82f',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
meatPrep: 'photo-1529692236671-f1f6cf9683ba',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chickenPieces: 'photo-1587593810167-a84920ea0781',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
rawChicken: 'photo-1621996346565-e3dbc646d9a9',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beefSteaks: 'photo-1558030006-450675393462',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beefRibeye: 'photo-1559847844-5315695dadae',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lambRack: 'photo-1615937657715-bc7b4b7962c1',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beefCubes: 'photo-1546833999-b9f581a1996d',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beefMince: 'photo-1603048297172-c92544798d5a',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lambLeg: 'photo-1544025162-d76694265947',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lambChops: 'photo-1574672280600-4accfa5b6f98',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
salmonFillet: 'photo-1544551763-46a013bb70d5',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
rawPrawns: 'photo-1565680018434-b513d5e5fd47',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
seafoodDisplay: 'photo-1504674900247-0877df9cc836',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} as const;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const IMAGES = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
logo: '/images/kottgard-logo.jpeg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
hero: unsplashUrl(RAW.butcherCounter, 2560),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
aboutMeat: unsplashUrl(RAW.meatPrep, 1920),
|
||||
// Line 50: categories: {
|
||||
categories: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
chicken: unsplashUrl(RAW.chickenPieces, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
beef: unsplashUrl(RAW.beefSteaks, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
lamb: unsplashUrl(RAW.lambRack, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
fish: unsplashUrl(RAW.salmonFillet, 1400),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 56: products: {
|
||||
products: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'chicken-whole': unsplashUrl(RAW.rawChicken, 1400),
|
||||
// Line 58: 'chicken-breast': unsplashUrl(RAW.chickenPieces, 1400, {
|
||||
'chicken-breast': unsplashUrl(RAW.chickenPieces, 1400, {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
height: 1000,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
crop: 'center',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}),
|
||||
// Line 62: 'chicken-thighs': unsplashUrl(RAW.rawChicken, 1400, {
|
||||
'chicken-thighs': unsplashUrl(RAW.rawChicken, 1400, {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
height: 1100,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
crop: 'entropy',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}),
|
||||
// Line 66: 'chicken-wings': unsplashUrl(RAW.chickenPieces, 1400, {
|
||||
'chicken-wings': unsplashUrl(RAW.chickenPieces, 1400, {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
height: 900,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
crop: 'top',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'beef-nihari': unsplashUrl(RAW.beefCubes, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'beef-steak': unsplashUrl(RAW.beefRibeye, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'beef-mince': unsplashUrl(RAW.beefMince, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'beef-boneless': unsplashUrl(RAW.beefSteaks, 1400, { height: 1000, crop: 'center' }),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'lamb-shoulder': unsplashUrl(RAW.lambRack, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'lamb-leg': unsplashUrl(RAW.lambLeg, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'lamb-chops': unsplashUrl(RAW.lambChops, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'lamb-mince': unsplashUrl(RAW.lambChops, 1400, { height: 1000, crop: 'center' }),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'fish-salmon': unsplashUrl(RAW.salmonFillet, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'fish-rohu': unsplashUrl(RAW.seafoodDisplay, 1400, { height: 1100, crop: 'entropy' }),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'fish-prawns': unsplashUrl(RAW.rawPrawns, 1400),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
'fish-basa': unsplashUrl(RAW.salmonFillet, 1400, { height: 900, crop: 'center' }),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} as const;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const IMAGE_QUALITY = 90;
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/offers.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { WeeklyOffer } from '@/types';
|
||||
// Import from a relative file in the same project
|
||||
import { IMAGES } from './images';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const weeklyOffers: WeeklyOffer[] = [
|
||||
// Line 5: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'chicken-wings-pl',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
nameKey: 'offers.items.chickenWings',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'fresh',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 49.99,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
originalPrice: 59.99,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: IMAGES.products['chicken-wings'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsappProduct: 'Kycklingvingar färsk PL',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productSlug: 'chicken-wings',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 16: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'lamb-steak-ireland',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
nameKey: 'offers.items.lambSteak',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'halal',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 204.99,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: IMAGES.categories.lamb,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsappProduct: 'Lammstek färsk Ireland',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productSlug: 'lamb-leg',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Line 26: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'beef-mince-irl',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
nameKey: 'offers.items.beefMince',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'fresh',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 139.99,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
originalPrice: 159.99,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: IMAGES.products['beef-mince'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
whatsappProduct: 'Nötfärs 5% fett IRL',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
productSlug: 'beef-mince',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// End of array literal
|
||||
];
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/product-i18n.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Product } from '@/types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript type alias — union or shorthand for complex types
|
||||
type Translator = (path: string, params?: Record<string, string | number>) => string;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface LocalizedProduct {
|
||||
// Line 6: id: string;
|
||||
id: string;
|
||||
// Line 7: slug: string;
|
||||
slug: string;
|
||||
// Line 8: category: Product['category'];
|
||||
category: Product['category'];
|
||||
// Line 9: name: string;
|
||||
name: string;
|
||||
// Line 10: description: string;
|
||||
description: string;
|
||||
// Line 11: longDescription: string;
|
||||
longDescription: string;
|
||||
// Line 12: price: number;
|
||||
price: number;
|
||||
// Line 13: priceUnit: string;
|
||||
priceUnit: string;
|
||||
// Line 14: image: string;
|
||||
image: string;
|
||||
// Line 15: images: string[];
|
||||
images: string[];
|
||||
// Line 16: badge?: string;
|
||||
badge?: string;
|
||||
// Line 17: inStock: boolean;
|
||||
inStock: boolean;
|
||||
// Line 18: featured: boolean;
|
||||
featured: boolean;
|
||||
// Line 19: weight?: string;
|
||||
weight?: string;
|
||||
// Line 20: tags: string[];
|
||||
tags: string[];
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function localizeProduct(product: Product, t: Translator): LocalizedProduct {
|
||||
// Line 24: const base = `products.${product.id}`;
|
||||
const base = `products.${product.id}`;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Return value from function
|
||||
return {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
...product,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: t(`${base}.name`),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t(`${base}.description`),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
longDescription: t(`${base}.longDescription`),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnit: t(`priceUnit.${product.priceUnitKey}`),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badge: product.badgeKey ? t(`badges.${product.badgeKey}`) : undefined,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function localizeCategory(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
categoryId: Product['category'],
|
||||
// Line 38: t: Translator
|
||||
t: Translator
|
||||
// Line 39: ): { name: string; description: string } {
|
||||
): { name: string; description: string } {
|
||||
// Return value from function
|
||||
return {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: t(`categories.${categoryId}.name`),
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
description: t(`categories.${categoryId}.description`),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/products.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Product } from '@/types';
|
||||
// Import from a relative file in the same project
|
||||
import { IMAGES } from './images';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
const P = IMAGES.products;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const products: Product[] = [
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'chicken-whole',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'whole-chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 129,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perBird',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['chicken-whole'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['chicken-whole'], P['chicken-breast']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'bestseller',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '1.2–1.5 kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'whole', 'popular'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'chicken-breast',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'chicken-breast',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 99,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perPack',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['chicken-breast'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['chicken-breast'], P['chicken-whole']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g–1kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'boneless', 'lean'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'chicken-thighs',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'chicken-thighs',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 85,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perPack',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['chicken-thighs'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['chicken-thighs']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g–1kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'juicy'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'chicken-wings',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'chicken-wings',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'chicken',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 79,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perPack',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['chicken-wings'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['chicken-wings']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'party'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'beef-nihari',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'beef-for-nihari',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'beef',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 149,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['beef-nihari'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['beef-nihari'], P['beef-steak']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'chefsPick',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '1 kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'traditional'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'beef-steak',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'premium-beef-steak',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'beef',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 229,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['beef-steak'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['beef-steak'], P['beef-nihari']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'premium',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '1 kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['premium', 'steak'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'beef-mince',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'beef-mince',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'beef',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 119,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['beef-mince'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['beef-mince']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g–1kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'mince'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'beef-boneless',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'boneless-beef-cubes',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'beef',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 169,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['beef-boneless'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['beef-boneless']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '1 kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'boneless'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'lamb-shoulder',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'lamb-shoulder',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'lamb',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 189,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['lamb-shoulder'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['lamb-shoulder'], P['lamb-leg']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'popular',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '1 kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'traditional'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'lamb-leg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'lamb-leg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'lamb',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 219,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['lamb-leg'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['lamb-leg']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '1–2 kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['premium', 'roast'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'lamb-chops',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'lamb-chops',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'lamb',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 249,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['lamb-chops'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['lamb-chops']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'premium',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['premium', 'grill'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'lamb-mince',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'lamb-mince',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'lamb',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 159,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['lamb-mince'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['lamb-mince']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g–1kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'mince'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'fish-salmon',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'atlantic-salmon-fillet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 199,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['fish-salmon'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['fish-salmon'], P['fish-rohu']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
badgeKey: 'freshCatch',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'fillet'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'fish-rohu',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'rohu-fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 119,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['fish-rohu'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['fish-rohu']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '1–1.5 kg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'whole'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'fish-prawns',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'jumbo-prawns',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 179,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['fish-prawns'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['fish-prawns']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'seafood'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Product catalog entry — demo data shown in shop and product pages
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'fish-basa',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
slug: 'basa-fillet',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
category: 'fish',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
price: 109,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
priceUnitKey: 'perKg',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
image: P['fish-basa'],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
images: [P['fish-basa']],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
inStock: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
featured: false,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
weight: '500g',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
tags: ['fresh', 'fillet', 'mild'],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// End of array literal
|
||||
];
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getProductBySlug(slug: string): Product | undefined {
|
||||
// Return value from function
|
||||
return products.find((p) => p.slug === slug);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getProductsByCategory(category: string): Product[] {
|
||||
// Return value from function
|
||||
return products.filter((p) => p.category === category);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getFeaturedProducts(): Product[] {
|
||||
// Return value from function
|
||||
return products.filter((p) => p.featured);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/lib/utils.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Import external package or local module
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
// Return value from function
|
||||
return clsx(inputs);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function formatPrice(price: number, locale = 'sv-SE'): string {
|
||||
// Return value from function
|
||||
return new Intl.NumberFormat(locale, {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
style: 'currency',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
currency: 'SEK',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}).format(price);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function getFormatLocale(locale: string): string {
|
||||
// Line 15: const map: Record<string, string> = {
|
||||
const map: Record<string, string> = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
en: 'en-US',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
sv: 'sv-SE',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
ur: 'ur-PK',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Return value from function
|
||||
return map[locale] ?? 'en-US';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function formatDate(date: string): string {
|
||||
// Return value from function
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
year: 'numeric',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
month: 'long',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
day: 'numeric',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}).format(new Date(date));
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/store/auth.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { create } from 'zustand';
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { persist } from 'zustand/middleware';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Address, Order, User } from '@/types';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { DEMO_EMAIL } from '@/lib/constants';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { DEMO_ORDERS } from '@/lib/demo-orders';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface AuthState {
|
||||
// Line 8: user: User | null;
|
||||
user: User | null;
|
||||
// Line 9: orders: Order[];
|
||||
orders: Order[];
|
||||
// Line 10: isAuthenticated: boolean;
|
||||
isAuthenticated: boolean;
|
||||
// Line 11: login: (email: string, password: string) => boolean;
|
||||
login: (email: string, password: string) => boolean;
|
||||
// Line 12: register: (data: {
|
||||
register: (data: {
|
||||
// Line 13: name: string;
|
||||
name: string;
|
||||
// Line 14: email: string;
|
||||
email: string;
|
||||
// Line 15: password: string;
|
||||
password: string;
|
||||
// Line 16: phone: string;
|
||||
phone: string;
|
||||
// Line 17: address: Address;
|
||||
address: Address;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}) => boolean;
|
||||
// Line 19: logout: () => void;
|
||||
logout: () => void;
|
||||
// Line 20: updateProfile: (data: Partial<User>) => void;
|
||||
updateProfile: (data: Partial<User>) => void;
|
||||
// Line 21: addOrder: (order: Order) => void;
|
||||
addOrder: (order: Order) => void;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 24: const DEMO_USER: User = {
|
||||
const DEMO_USER: User = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: 'demo-1',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: 'Ahmed Khan',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: DEMO_EMAIL,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: '+46 72 585 50 50',
|
||||
// Line 29: address: {
|
||||
address: {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
street: 'Tingvallavägen 11',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
city: 'Märsta',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
state: 'Stockholm',
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
zip: '195 31',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createdAt: '2025-03-15T10:00:00.000Z',
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
// Zustand persist — save store to localStorage between visits
|
||||
persist(
|
||||
// Line 40: (set, get) => ({
|
||||
(set, get) => ({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
user: null,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orders: [],
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
isAuthenticated: false,
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 45: login: (email, password) => {
|
||||
login: (email, password) => {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (email === DEMO_EMAIL && password === 'demo123') {
|
||||
// Line 47: const existingOrders = get().orders;
|
||||
const existingOrders = get().orders;
|
||||
// Line 48: set({
|
||||
set({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
user: DEMO_USER,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
isAuthenticated: true,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
orders: existingOrders.length > 0 ? existingOrders : DEMO_ORDERS,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Return value from function
|
||||
return true;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Line 55: const stored = get().user;
|
||||
const stored = get().user;
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (stored && stored.email === email) {
|
||||
// Line 57: set({ isAuthenticated: true });
|
||||
set({ isAuthenticated: true });
|
||||
// Return value from function
|
||||
return true;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Return value from function
|
||||
return false;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 63: register: (data) => {
|
||||
register: (data) => {
|
||||
// Line 64: const user: User = {
|
||||
const user: User = {
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id: `user-${Date.now()}`,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
name: data.name,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
email: data.email,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
phone: data.phone,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
address: data.address,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
createdAt: new Date().toISOString(),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
};
|
||||
// Line 72: set({ user, isAuthenticated: true });
|
||||
set({ user, isAuthenticated: true });
|
||||
// Return value from function
|
||||
return true;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
logout: () => set({ isAuthenticated: false }),
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 78: updateProfile: (data) => {
|
||||
updateProfile: (data) => {
|
||||
// Line 79: const current = get().user;
|
||||
const current = get().user;
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (current) {
|
||||
// Line 81: set({ user: { ...current, ...data } });
|
||||
set({ user: { ...current, ...data } });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 85: addOrder: (order) => {
|
||||
addOrder: (order) => {
|
||||
// Line 86: set({ orders: [order, ...get().orders] });
|
||||
set({ orders: [order, ...get().orders] });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}),
|
||||
// Line 89: { name: 'kott-gard-auth' }
|
||||
{ name: 'kott-gard-auth' }
|
||||
// Line 90: )
|
||||
)
|
||||
// Line 91: );
|
||||
);
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/store/cart.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { create } from 'zustand';
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { persist } from 'zustand/middleware';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { CartItem, Product, ProductCustomization } from '@/types';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { getCartItemKey } from '@/lib/customization';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface CartState {
|
||||
// Line 7: items: CartItem[];
|
||||
items: CartItem[];
|
||||
// Line 8: addItem: (
|
||||
addItem: (
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
product: Product,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization: ProductCustomization,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customizationLabel: string,
|
||||
// Line 12: quantity?: number
|
||||
quantity?: number
|
||||
// Line 13: ) => void;
|
||||
) => void;
|
||||
// Line 14: removeItem: (id: string) => void;
|
||||
removeItem: (id: string) => void;
|
||||
// Line 15: updateQuantity: (id: string, quantity: number) => void;
|
||||
updateQuantity: (id: string, quantity: number) => void;
|
||||
// Line 16: clearCart: () => void;
|
||||
clearCart: () => void;
|
||||
// Line 17: getTotal: () => number;
|
||||
getTotal: () => number;
|
||||
// Line 18: getItemCount: () => number;
|
||||
getItemCount: () => number;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const useCartStore = create<CartState>()(
|
||||
// Zustand persist — save store to localStorage between visits
|
||||
persist(
|
||||
// Line 23: (set, get) => ({
|
||||
(set, get) => ({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
items: [],
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 26: addItem: (product, customization, customizationLabel, qua...
|
||||
addItem: (product, customization, customizationLabel, quantity = 1) => {
|
||||
// Line 27: const id = getCartItemKey(product.id, customization);
|
||||
const id = getCartItemKey(product.id, customization);
|
||||
// Line 28: const label = customizationLabel;
|
||||
const label = customizationLabel;
|
||||
// Array.find — get first matching item or undefined
|
||||
const existing = get().items.find((item) => item.id === id);
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (existing) {
|
||||
// Line 32: set({
|
||||
set({
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
items: get().items.map((item) =>
|
||||
// Line 34: item.id === id
|
||||
item.id === id
|
||||
// Line 35: ? { ...item, quantity: item.quantity + quantity }
|
||||
? { ...item, quantity: item.quantity + quantity }
|
||||
// Line 36: : item
|
||||
: item
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} else {
|
||||
// Line 40: set({
|
||||
set({
|
||||
// Line 41: items: [
|
||||
items: [
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
...get().items,
|
||||
// Line 43: {
|
||||
{
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
id,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
product,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
quantity,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customization,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
customizationLabel: label,
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// End of array literal
|
||||
],
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 55: removeItem: (id) => {
|
||||
removeItem: (id) => {
|
||||
// Array.filter — keep items matching condition (search, category)
|
||||
set({ items: get().items.filter((item) => item.id !== id) });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 59: updateQuantity: (id, quantity) => {
|
||||
updateQuantity: (id, quantity) => {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (quantity <= 0) {
|
||||
// Line 61: get().removeItem(id);
|
||||
get().removeItem(id);
|
||||
// Line 62: return;
|
||||
return;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Line 64: set({
|
||||
set({
|
||||
// Array.map — transform each item (often render a list of components)
|
||||
items: get().items.map((item) =>
|
||||
// Line 66: item.id === id ? { ...item, quantity } : item
|
||||
item.id === id ? { ...item, quantity } : item
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
});
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
clearCart: () => set({ items: [] }),
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 73: getTotal: () =>
|
||||
getTotal: () =>
|
||||
// Array.reduce — accumulate single value (cart total, item count)
|
||||
get().items.reduce(
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
(sum, item) => sum + item.product.price * item.quantity,
|
||||
// Line 76: 0
|
||||
0
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
),
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 79: getItemCount: () =>
|
||||
getItemCount: () =>
|
||||
// Array.reduce — accumulate single value (cart total, item count)
|
||||
get().items.reduce((sum, item) => sum + item.quantity, 0),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}),
|
||||
// Line 82: { name: 'kott-gard-cart' }
|
||||
{ name: 'kott-gard-cart' }
|
||||
// Line 83: )
|
||||
)
|
||||
// Line 84: );
|
||||
);
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/store/locale.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { create } from 'zustand';
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { persist } from 'zustand/middleware';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { DEFAULT_LOCALE, Locale } from '@/i18n/types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface LocaleState {
|
||||
// Line 6: locale: Locale;
|
||||
locale: Locale;
|
||||
// Line 7: setLocale: (locale: Locale) => void;
|
||||
setLocale: (locale: Locale) => void;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const useLocaleStore = create<LocaleState>()(
|
||||
// Zustand persist — save store to localStorage between visits
|
||||
persist(
|
||||
// Line 12: (set) => ({
|
||||
(set) => ({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
locale: DEFAULT_LOCALE,
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
setLocale: (locale) => set({ locale }),
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}),
|
||||
// Line 16: { name: 'kott-gard-locale' }
|
||||
{ name: 'kott-gard-locale' }
|
||||
// Line 17: )
|
||||
)
|
||||
// Line 18: );
|
||||
);
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/store/wishlist.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { create } from 'zustand';
|
||||
// Zustand — simple global state store (cart, auth, locale)
|
||||
import { persist } from 'zustand/middleware';
|
||||
// Import project module (@/ alias = src/ folder in tsconfig)
|
||||
import { Product } from '@/types';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// TypeScript interface — contract for object properties and methods
|
||||
interface WishlistState {
|
||||
// Line 6: items: Product[];
|
||||
items: Product[];
|
||||
// Line 7: addItem: (product: Product) => void;
|
||||
addItem: (product: Product) => void;
|
||||
// Line 8: removeItem: (productId: string) => void;
|
||||
removeItem: (productId: string) => void;
|
||||
// Line 9: isInWishlist: (productId: string) => boolean;
|
||||
isInWishlist: (productId: string) => boolean;
|
||||
// Line 10: toggleItem: (product: Product) => void;
|
||||
toggleItem: (product: Product) => void;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export constant — shared config/data imported elsewhere
|
||||
export const useWishlistStore = create<WishlistState>()(
|
||||
// Zustand persist — save store to localStorage between visits
|
||||
persist(
|
||||
// Line 15: (set, get) => ({
|
||||
(set, get) => ({
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
items: [],
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 18: addItem: (product) => {
|
||||
addItem: (product) => {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (!get().isInWishlist(product.id)) {
|
||||
// Line 20: set({ items: [...get().items, product] });
|
||||
set({ items: [...get().items, product] });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 24: removeItem: (productId) => {
|
||||
removeItem: (productId) => {
|
||||
// Array.filter — keep items matching condition (search, category)
|
||||
set({ items: get().items.filter((p) => p.id !== productId) });
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 28: isInWishlist: (productId) =>
|
||||
isInWishlist: (productId) =>
|
||||
// Property or array item — trailing comma allowed in TypeScript
|
||||
get().items.some((p) => p.id === productId),
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Line 31: toggleItem: (product) => {
|
||||
toggleItem: (product) => {
|
||||
// Conditional branch — different behavior based on runtime value
|
||||
if (get().isInWishlist(product.id)) {
|
||||
// Line 33: get().removeItem(product.id);
|
||||
get().removeItem(product.id);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
} else {
|
||||
// Line 35: get().addItem(product);
|
||||
get().addItem(product);
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
},
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}),
|
||||
// Line 39: { name: 'kott-gard-wishlist' }
|
||||
{ name: 'kott-gard-wishlist' }
|
||||
// Line 40: )
|
||||
)
|
||||
// Line 41: );
|
||||
);
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* ANNOTATED COPY — every line explained
|
||||
* Source: src/types/index.ts
|
||||
* NOT used by the app — read this to learn how the real file works
|
||||
*/
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type Category = 'chicken' | 'beef' | 'lamb' | 'fish';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type CutCount = 4 | 8 | 10 | 12;
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type CuttingStyleKey = 'nihari' | 'karahi' | 'qeema' | 'boneless' | 'steak';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type MeatCategory = 'chicken' | 'beef' | 'lamb';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface MeatCustomization {
|
||||
// Line 10: type: MeatCategory;
|
||||
type: MeatCategory;
|
||||
// Line 11: cuts: CutCount;
|
||||
cuts: CutCount;
|
||||
// Line 12: cuttingStyle: CuttingStyleKey;
|
||||
cuttingStyle: CuttingStyleKey;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type ProductCustomization = MeatCustomization | { type: 'fish' };
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Named export — utility function other files can import
|
||||
export function isMeatCustomization(
|
||||
// Line 18: customization: ProductCustomization
|
||||
customization: ProductCustomization
|
||||
// Line 19: ): customization is MeatCustomization {
|
||||
): customization is MeatCustomization {
|
||||
// Return value from function
|
||||
return customization.type !== 'fish';
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type PriceUnitKey = 'perBird' | 'perPack' | 'perKg';
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type BadgeKey = 'bestseller' | 'chefsPick' | 'premium' | 'popular' | 'freshCatch';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface Product {
|
||||
// Line 27: id: string;
|
||||
id: string;
|
||||
// Line 28: slug: string;
|
||||
slug: string;
|
||||
// Line 29: category: Category;
|
||||
category: Category;
|
||||
// Line 30: price: number;
|
||||
price: number;
|
||||
// Line 31: priceUnitKey: PriceUnitKey;
|
||||
priceUnitKey: PriceUnitKey;
|
||||
// Line 32: image: string;
|
||||
image: string;
|
||||
// Line 33: images: string[];
|
||||
images: string[];
|
||||
// Line 34: badgeKey?: BadgeKey;
|
||||
badgeKey?: BadgeKey;
|
||||
// Line 35: inStock: boolean;
|
||||
inStock: boolean;
|
||||
// Line 36: featured: boolean;
|
||||
featured: boolean;
|
||||
// Line 37: weight?: string;
|
||||
weight?: string;
|
||||
// Line 38: tags: string[];
|
||||
tags: string[];
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface CartItem {
|
||||
// Line 42: id: string;
|
||||
id: string;
|
||||
// Line 43: product: Product;
|
||||
product: Product;
|
||||
// Line 44: quantity: number;
|
||||
quantity: number;
|
||||
// Line 45: customization: ProductCustomization;
|
||||
customization: ProductCustomization;
|
||||
// Line 46: customizationLabel: string;
|
||||
customizationLabel: string;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface User {
|
||||
// Line 50: id: string;
|
||||
id: string;
|
||||
// Line 51: name: string;
|
||||
name: string;
|
||||
// Line 52: email: string;
|
||||
email: string;
|
||||
// Line 53: phone: string;
|
||||
phone: string;
|
||||
// Line 54: address: Address;
|
||||
address: Address;
|
||||
// Line 55: createdAt: string;
|
||||
createdAt: string;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface Address {
|
||||
// Line 59: street: string;
|
||||
street: string;
|
||||
// Line 60: city: string;
|
||||
city: string;
|
||||
// Line 61: state: string;
|
||||
state: string;
|
||||
// Line 62: zip: string;
|
||||
zip: string;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface Order {
|
||||
// Line 66: id: string;
|
||||
id: string;
|
||||
// Line 67: items: CartItem[];
|
||||
items: CartItem[];
|
||||
// Line 68: total: number;
|
||||
total: number;
|
||||
// Line 69: status: 'pending' | 'confirmed' | 'preparing' | 'out-for-...
|
||||
status: 'pending' | 'confirmed' | 'preparing' | 'out-for-delivery' | 'delivered';
|
||||
// Line 70: createdAt: string;
|
||||
createdAt: string;
|
||||
// Line 71: deliveryAddress: Address;
|
||||
deliveryAddress: Address;
|
||||
// Line 72: paymentMethod: string;
|
||||
paymentMethod: string;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type SortOption = 'featured' | 'price-asc' | 'price-desc' | 'name';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export type OfferBadgeKey = 'fresh' | 'halal';
|
||||
// (blank line — separates logical blocks for readability)
|
||||
// Export TypeScript type — defines data shape used across the app
|
||||
export interface WeeklyOffer {
|
||||
// Line 80: id: string;
|
||||
id: string;
|
||||
// Line 81: nameKey: string;
|
||||
nameKey: string;
|
||||
// Line 82: badgeKey: OfferBadgeKey;
|
||||
badgeKey: OfferBadgeKey;
|
||||
// Line 83: price: number;
|
||||
price: number;
|
||||
// Line 84: originalPrice?: number;
|
||||
originalPrice?: number;
|
||||
// Line 85: priceUnitKey: PriceUnitKey;
|
||||
priceUnitKey: PriceUnitKey;
|
||||
// Line 86: image: string;
|
||||
image: string;
|
||||
// Line 87: whatsappProduct: string;
|
||||
whatsappProduct: string;
|
||||
// Line 88: productSlug: string;
|
||||
productSlug: string;
|
||||
// Closing brace — end of block (function, if, object, JSX)
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Generates Kottgard-Website-Guide.docx
|
||||
* Run: node docs/generate-guide-docx.mjs
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import {
|
||||
Document,
|
||||
Packer,
|
||||
Paragraph,
|
||||
TextRun,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
HeadingLevel,
|
||||
AlignmentType,
|
||||
BorderStyle,
|
||||
WidthType,
|
||||
ShadingType,
|
||||
PageBreak,
|
||||
LevelFormat,
|
||||
} from 'docx';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const outPath = path.join(__dirname, 'Kottgard-Website-Guide.docx');
|
||||
|
||||
const border = { style: BorderStyle.SINGLE, size: 1, color: 'CCCCCC' };
|
||||
const borders = { top: border, bottom: border, left: border, right: border };
|
||||
const tableWidth = 9360;
|
||||
|
||||
function cell(text, width, fill = 'FFFFFF') {
|
||||
return new TableCell({
|
||||
borders,
|
||||
width: { size: width, type: WidthType.DXA },
|
||||
shading: { fill, type: ShadingType.CLEAR },
|
||||
margins: { top: 80, bottom: 80, left: 120, right: 120 },
|
||||
children: [new Paragraph({ children: [new TextRun(text)] })],
|
||||
});
|
||||
}
|
||||
|
||||
function headerRow(cols, widths) {
|
||||
return new TableRow({
|
||||
children: cols.map((t, i) => cell(t, widths[i], 'D5E8F0')),
|
||||
});
|
||||
}
|
||||
|
||||
function dataRow(cols, widths) {
|
||||
return new TableRow({
|
||||
children: cols.map((t, i) => cell(t, widths[i])),
|
||||
});
|
||||
}
|
||||
|
||||
function table(columnWidths, rows) {
|
||||
return new Table({
|
||||
width: { size: tableWidth, type: WidthType.DXA },
|
||||
columnWidths,
|
||||
rows,
|
||||
});
|
||||
}
|
||||
|
||||
function h1(text) {
|
||||
return new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun(text)] });
|
||||
}
|
||||
function h2(text) {
|
||||
return new Paragraph({ heading: HeadingLevel.HEADING_2, children: [new TextRun(text)] });
|
||||
}
|
||||
function p(text) {
|
||||
return new Paragraph({ spacing: { after: 200 }, children: [new TextRun(text)] });
|
||||
}
|
||||
function bullet(ref, text) {
|
||||
return new Paragraph({
|
||||
numbering: { reference: ref, level: 0 },
|
||||
children: [new TextRun(text)],
|
||||
});
|
||||
}
|
||||
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
default: { document: { run: { font: 'Arial', size: 22 } } },
|
||||
paragraphStyles: [
|
||||
{
|
||||
id: 'Heading1',
|
||||
name: 'Heading 1',
|
||||
basedOn: 'Normal',
|
||||
next: 'Normal',
|
||||
quickFormat: true,
|
||||
run: { size: 32, bold: true, font: 'Arial' },
|
||||
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 },
|
||||
},
|
||||
{
|
||||
id: 'Heading2',
|
||||
name: 'Heading 2',
|
||||
basedOn: 'Normal',
|
||||
next: 'Normal',
|
||||
quickFormat: true,
|
||||
run: { size: 26, bold: true, font: 'Arial' },
|
||||
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 },
|
||||
},
|
||||
],
|
||||
},
|
||||
numbering: {
|
||||
config: [
|
||||
{
|
||||
reference: 'bullets',
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: LevelFormat.BULLET,
|
||||
text: '\u2022',
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
properties: {
|
||||
page: {
|
||||
size: { width: 12240, height: 15840 },
|
||||
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 },
|
||||
},
|
||||
},
|
||||
children: [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 400 },
|
||||
children: [
|
||||
new TextRun({ text: 'Kött Gård', bold: true, size: 48 }),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 600 },
|
||||
children: [
|
||||
new TextRun({
|
||||
text: 'Website Visual & Programming Guide',
|
||||
size: 32,
|
||||
color: '8B1F1F',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
p('Premium Halal meat e-commerce — Märsta, Sweden'),
|
||||
p('Document version: June 2026'),
|
||||
p('Companion files: docs/annotated/ (line-by-line code comments)'),
|
||||
|
||||
new Paragraph({ children: [new PageBreak()] }),
|
||||
|
||||
h1('1. What is this website?'),
|
||||
p(
|
||||
'Kött Gård is a Next.js e-commerce front-end for ordering fresh Halal chicken, beef, lamb, and fish. Customers browse products, customize cuts (Nihari, Karahi, piece counts), add to cart, checkout, and view order history. Languages: Swedish, English, Urdu (RTL).'
|
||||
),
|
||||
|
||||
h2('Demo login'),
|
||||
table(
|
||||
[3120, 6240],
|
||||
[
|
||||
headerRow(['Field', 'Value'], [3120, 6240]),
|
||||
dataRow(['Email', 'demo@kottgard.se'], [3120, 6240]),
|
||||
dataRow(['Password', 'demo123'], [3120, 6240]),
|
||||
dataRow(['Orders', '3 sample orders in Account'], [3120, 6240]),
|
||||
]
|
||||
),
|
||||
|
||||
h1('2. Homepage layout (visual map)'),
|
||||
p('When a customer opens https://kottgard.se/, they scroll through these sections:'),
|
||||
table(
|
||||
[800, 2200, 3160, 3200],
|
||||
[
|
||||
headerRow(['#', 'Section', 'File', 'Customer action'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['1', 'Hero', 'Hero.tsx', 'Shop now / WhatsApp'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['2', 'Trust badges', 'TrustBadges.tsx', 'Click → About'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['3', 'About preview', 'AboutPreview.tsx', 'Read more → /about'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['4', 'Categories', 'CategoryGrid.tsx', 'Chicken/Beef/Lamb/Fish'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['5', 'Featured', 'FeaturedProducts.tsx', 'Product cards'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['6', 'How it works', 'HowItWorks.tsx', 'Order steps'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['7', 'Weekly offers', 'WeeklyOffers.tsx', 'Deals → products'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['8', 'CTA', 'CTA.tsx', 'Browse selection'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['9', 'Social', 'SocialFollow.tsx', 'Facebook / Instagram'], [800, 2200, 3160, 3200]),
|
||||
dataRow(['10', 'Contact', 'ContactPreview.tsx', 'Map / phone / about'], [800, 2200, 3160, 3200]),
|
||||
]
|
||||
),
|
||||
|
||||
h1('3. All pages & routes'),
|
||||
table(
|
||||
[2800, 3360, 3200],
|
||||
[
|
||||
headerRow(['URL', 'File', 'Description'], [2800, 3360, 3200]),
|
||||
dataRow(['/', 'app/page.tsx', 'Homepage'], [2800, 3360, 3200]),
|
||||
dataRow(['/shop', 'app/shop/page.tsx', 'Catalog + search + filters'], [2800, 3360, 3200]),
|
||||
dataRow(['/product/[slug]', 'app/product/[slug]/page.tsx', 'Detail + customize'], [2800, 3360, 3200]),
|
||||
dataRow(['/cart', 'app/cart/page.tsx', 'Shopping cart'], [2800, 3360, 3200]),
|
||||
dataRow(['/checkout', 'app/checkout/page.tsx', 'Place order (demo)'], [2800, 3360, 3200]),
|
||||
dataRow(['/login', 'app/login/page.tsx', 'Auth'], [2800, 3360, 3200]),
|
||||
dataRow(['/account', 'app/account/page.tsx', 'Profile + orders'], [2800, 3360, 3200]),
|
||||
dataRow(['/wishlist', 'app/wishlist/page.tsx', 'Saved items'], [2800, 3360, 3200]),
|
||||
dataRow(['/about', 'app/about/page.tsx', 'Company + privacy + terms'], [2800, 3360, 3200]),
|
||||
]
|
||||
),
|
||||
|
||||
new Paragraph({ children: [new PageBreak()] }),
|
||||
|
||||
h1('4. Programming languages & frameworks'),
|
||||
h2('TypeScript'),
|
||||
p(
|
||||
'TypeScript adds types to JavaScript. Example: Product interface ensures every product has id, slug, price. The compiler errors if you typo product.slugg.'
|
||||
),
|
||||
h2('React'),
|
||||
p(
|
||||
'React builds UI from components (functions returning JSX). State (useState) updates what users see. Props pass data parent → child.'
|
||||
),
|
||||
h2('Next.js 13 App Router'),
|
||||
bullet('bullets', 'app/ folder = routes. page.tsx = page, layout.tsx = wrapper.'),
|
||||
bullet('bullets', 'Server Components default — less JavaScript sent to browser.'),
|
||||
bullet('bullets', "'use client' = component runs in browser (hooks, clicks)."),
|
||||
h2('Zustand'),
|
||||
p(
|
||||
'Global stores: useCartStore, useAuthStore, useWishlistStore. persist middleware saves to localStorage so cart survives refresh.'
|
||||
),
|
||||
h2('Tailwind CSS'),
|
||||
p(
|
||||
'Classes like bg-brand-700 text-white px-4 py-2 style elements. Colors defined in tailwind.config.ts (burgundy, cream, gold).'
|
||||
),
|
||||
|
||||
h1('5. Data flow diagram (text)'),
|
||||
p('Homepage → Shop (filter URL) → Product page → addItem() → Cart store → Checkout → Order in Auth store → Account page'),
|
||||
p('Images: lib/images.ts → products.ts → ProductCard / Hero → AppImage → next/image → Unsplash CDN'),
|
||||
p('Text: i18n/locales/sv|en|ur.ts → useTranslation() → t("key") in components'),
|
||||
|
||||
h1('6. Annotated code (line-by-line)'),
|
||||
p('Location: Kottgard/docs/annotated/'),
|
||||
table(
|
||||
[4200, 5160],
|
||||
[
|
||||
headerRow(['File', 'Explains'], [4200, 5160]),
|
||||
dataRow(['01-homepage.annotated.tsx', 'How homepage composes sections'], [4200, 5160]),
|
||||
dataRow(['02-root-layout.annotated.tsx', 'Fonts, SEO, shell layout'], [4200, 5160]),
|
||||
dataRow(['03-shop-page.annotated.tsx', 'URL sync, useMemo filtering'], [4200, 5160]),
|
||||
dataRow(['04-header.annotated.tsx', 'Navigation + Zustand badges'], [4200, 5160]),
|
||||
dataRow(['05-images.annotated.ts', 'Unsplash URL builder'], [4200, 5160]),
|
||||
dataRow(['06-cart-store.annotated.ts', 'Cart logic + persist'], [4200, 5160]),
|
||||
]
|
||||
),
|
||||
p(
|
||||
'Each line has comments explaining WHAT the code does and WHY. These are learning copies — not imported by the live app.'
|
||||
),
|
||||
|
||||
h1('7. Brand colors'),
|
||||
table(
|
||||
[3120, 3120, 3120],
|
||||
[
|
||||
headerRow(['Name', 'Hex', 'Use'], [3120, 3120, 3120]),
|
||||
dataRow(['Burgundy (brand)', '#8B1F1F', 'Header, buttons, footer'], [3120, 3120, 3120]),
|
||||
dataRow(['Cream', 'Warm off-white', 'Page backgrounds'], [3120, 3120, 3120]),
|
||||
dataRow(['Gold', 'Accent', 'Prices, badges, CTAs'], [3120, 3120, 3120]),
|
||||
]
|
||||
),
|
||||
|
||||
h1('8. How to run locally'),
|
||||
bullet('bullets', 'npm install'),
|
||||
bullet('bullets', 'npm run dev → http://localhost:3000'),
|
||||
bullet('bullets', 'npm run build → production check'),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const buffer = await Packer.toBuffer(doc);
|
||||
fs.writeFileSync(outPath, buffer);
|
||||
console.log('Written:', outPath);
|
||||
@@ -0,0 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
images: {
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 2560],
|
||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "kott-gard",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"annotate": "node docs/annotate-all.mjs",
|
||||
"images:populate": "bash scripts/populate-site-images.sh",
|
||||
"images:fish": "bash scripts/update-fish-images.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.0",
|
||||
"lucide-react": "^0.344.0",
|
||||
"next": "13.5.6",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"docx": "^9.7.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "13.5.6",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
KÖTT GÅRD — ALL WEBSITE IMAGES (one folder)
|
||||
============================================
|
||||
|
||||
Every image on the website comes from THIS folder only.
|
||||
|
||||
HOW TO CHANGE AN IMAGE
|
||||
----------------------
|
||||
1. Find the filename below (e.g. beef-steak.jpg)
|
||||
2. Replace that file with your new photo (keep the SAME filename)
|
||||
3. Save and refresh the browser (Cmd+Shift+R)
|
||||
|
||||
You do NOT need to edit any code for normal image swaps.
|
||||
|
||||
|
||||
FILENAME GUIDE
|
||||
--------------
|
||||
|
||||
BRAND
|
||||
logo.jpeg Header & footer logo
|
||||
|
||||
HOME PAGE
|
||||
hero.jpg Big background on home page
|
||||
about.jpg About us section photo
|
||||
weekly-offers.jpg Weekly offers section background
|
||||
|
||||
SHOP CATEGORIES (home page tiles)
|
||||
category-chicken.jpg
|
||||
category-beef.jpg
|
||||
category-lamb.jpg
|
||||
category-fish.jpg
|
||||
|
||||
PRODUCTS (shop, product page, cart)
|
||||
chicken-whole.jpg Main photo
|
||||
chicken-whole-2.jpg Second photo on product page (optional)
|
||||
chicken-breast.jpg
|
||||
chicken-breast-2.jpg
|
||||
chicken-thighs.jpg
|
||||
chicken-wings.jpg
|
||||
beef-nihari.jpg
|
||||
beef-nihari-2.jpg
|
||||
beef-steak.jpg
|
||||
beef-steak-2.jpg
|
||||
beef-mince.jpg
|
||||
beef-boneless.jpg
|
||||
lamb-shoulder.jpg
|
||||
lamb-shoulder-2.jpg
|
||||
lamb-leg.jpg
|
||||
lamb-chops.jpg
|
||||
lamb-mince.jpg
|
||||
fish-salmon.jpg
|
||||
fish-salmon-2.jpg
|
||||
fish-rohu.jpg
|
||||
fish-prawns.jpg
|
||||
fish-basa.jpg
|
||||
|
||||
TIPS
|
||||
----
|
||||
• Use JPG or JPEG, about 1400px wide for products, 2560px for hero
|
||||
• Keep filenames lowercase with hyphens
|
||||
• Files ending in -2.jpg are extra gallery images on the product page
|
||||
• To re-download all images: npm run images:populate
|
||||
|
||||
Project path:
|
||||
Kottgard/public/images/site/
|
||||
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 480 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 834 KiB |
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 834 KiB |
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# Re-download default images into public/images/site/ (only image folder used by the site)
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SITE="$ROOT/public/images/site"
|
||||
mkdir -p "$SITE"
|
||||
|
||||
dl() {
|
||||
curl -fsSL "$1" -o "$2"
|
||||
}
|
||||
|
||||
# Keep existing logo if present; otherwise skip (add logo.jpeg manually)
|
||||
if [[ ! -f "$SITE/logo.jpeg" ]]; then
|
||||
echo "Note: add logo.jpeg to $SITE manually if missing."
|
||||
fi
|
||||
|
||||
dl "https://images.unsplash.com/photo-1607623814075-e51df1bdc82f?auto=format&fit=crop&w=2560&q=90" "$SITE/hero.jpg"
|
||||
dl "https://images.unsplash.com/photo-1529692236671-f1f6cf9683ba?auto=format&fit=crop&w=1920&q=90" "$SITE/about.jpg"
|
||||
cp "$SITE/hero.jpg" "$SITE/weekly-offers.jpg"
|
||||
|
||||
dl "https://images.unsplash.com/photo-1587593810167-a84920ea0781?auto=format&fit=crop&w=1400&q=90" "$SITE/category-chicken.jpg"
|
||||
dl "https://images.unsplash.com/photo-1558030006-450675393462?auto=format&fit=crop&w=1400&q=90" "$SITE/category-beef.jpg"
|
||||
dl "https://images.unsplash.com/photo-1615937657715-bc7b4b7962c1?auto=format&fit=crop&w=1400&q=90" "$SITE/category-lamb.jpg"
|
||||
dl "https://images.unsplash.com/photo-1544551763-46a013bb70d5?auto=format&fit=crop&w=1400&q=90" "$SITE/category-fish.jpg"
|
||||
|
||||
dl "https://images.unsplash.com/photo-1621996346565-e3dbc646d9a9?auto=format&fit=crop&w=1400&q=90" "$SITE/chicken-whole.jpg"
|
||||
dl "https://images.unsplash.com/photo-1587593810167-a84920ea0781?auto=format&fit=crop&w=1400&h=1000&crop=center&q=90" "$SITE/chicken-whole-2.jpg"
|
||||
dl "https://images.unsplash.com/photo-1587593810167-a84920ea0781?auto=format&fit=crop&w=1400&h=1000&crop=center&q=90" "$SITE/chicken-breast.jpg"
|
||||
cp "$SITE/chicken-whole.jpg" "$SITE/chicken-breast-2.jpg"
|
||||
dl "https://images.unsplash.com/photo-1621996346565-e3dbc646d9a9?auto=format&fit=crop&w=1400&h=1100&crop=entropy&q=90" "$SITE/chicken-thighs.jpg"
|
||||
dl "https://images.unsplash.com/photo-1587593810167-a84920ea0781?auto=format&fit=crop&w=1400&h=900&crop=top&q=90" "$SITE/chicken-wings.jpg"
|
||||
|
||||
dl "https://images.unsplash.com/photo-1546833999-b9f581a1996d?auto=format&fit=crop&w=1400&q=90" "$SITE/beef-nihari.jpg"
|
||||
dl "https://images.unsplash.com/photo-1559847844-5315695dadae?auto=format&fit=crop&w=1400&q=90" "$SITE/beef-nihari-2.jpg"
|
||||
dl "https://images.unsplash.com/photo-1559847844-5315695dadae?auto=format&fit=crop&w=1400&q=90" "$SITE/beef-steak.jpg"
|
||||
dl "https://images.unsplash.com/photo-1546833999-b9f581a1996d?auto=format&fit=crop&w=1400&q=90" "$SITE/beef-steak-2.jpg"
|
||||
dl "https://images.unsplash.com/photo-1603048297172-c92544798d5a?auto=format&fit=crop&w=1400&q=90" "$SITE/beef-mince.jpg"
|
||||
dl "https://images.unsplash.com/photo-1558030006-450675393462?auto=format&fit=crop&w=1400&h=1000&crop=center&q=90" "$SITE/beef-boneless.jpg"
|
||||
|
||||
dl "https://images.unsplash.com/photo-1615937657715-bc7b4b7962c1?auto=format&fit=crop&w=1400&q=90" "$SITE/lamb-shoulder.jpg"
|
||||
dl "https://images.unsplash.com/photo-1544025162-d76694265947?auto=format&fit=crop&w=1400&q=90" "$SITE/lamb-shoulder-2.jpg"
|
||||
dl "https://images.unsplash.com/photo-1544025162-d76694265947?auto=format&fit=crop&w=1400&q=90" "$SITE/lamb-leg.jpg"
|
||||
dl "https://images.unsplash.com/photo-1574672280600-4accfa5b6f98?auto=format&fit=crop&w=1400&q=90" "$SITE/lamb-chops.jpg"
|
||||
dl "https://images.unsplash.com/photo-1574672280600-4accfa5b6f98?auto=format&fit=crop&w=1400&h=1000&crop=center&q=90" "$SITE/lamb-mince.jpg"
|
||||
|
||||
dl "https://images.unsplash.com/photo-1544551763-46a013bb70d5?auto=format&fit=crop&w=1400&q=90" "$SITE/fish-salmon.jpg"
|
||||
dl "https://images.unsplash.com/photo-1504674900247-0877df9cc836?auto=format&fit=crop&w=1400&h=1100&crop=entropy&q=90" "$SITE/fish-salmon-2.jpg"
|
||||
dl "https://images.unsplash.com/photo-1504674900247-0877df9cc836?auto=format&fit=crop&w=1400&h=1100&crop=entropy&q=90" "$SITE/fish-rohu.jpg"
|
||||
dl "https://images.unsplash.com/photo-1565680018434-b513d5e5fd47?auto=format&fit=crop&w=1400&q=90" "$SITE/fish-prawns.jpg"
|
||||
dl "https://images.unsplash.com/photo-1544551763-46a013bb70d5?auto=format&fit=crop&w=1400&h=900&crop=center&q=90" "$SITE/fish-basa.jpg"
|
||||
|
||||
echo "Done. $(ls -1 "$SITE" | wc -l | tr -d ' ') files in $SITE"
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Replace fish images (frozen seafood style)
|
||||
set -euo pipefail
|
||||
SITE="$(cd "$(dirname "$0")/.." && pwd)/public/images/site"
|
||||
dl() { curl -fsSL "$1" -o "$2"; }
|
||||
|
||||
dl "https://images.unsplash.com/photo-1559339352-11d035aa65de?auto=format&fit=crop&w=1400&q=90" "$SITE/category-fish.jpg"
|
||||
dl "https://images.unsplash.com/photo-1559339352-11d035aa65de?auto=format&fit=crop&w=1400&h=1000&crop=center&q=90" "$SITE/fish-salmon.jpg"
|
||||
dl "https://images.unsplash.com/photo-1544551763-46a013bb70d5?auto=format&fit=crop&w=1400&q=90" "$SITE/fish-salmon-2.jpg"
|
||||
dl "https://images.unsplash.com/photo-1504674900247-0877df9cc836?auto=format&fit=crop&w=1400&h=1100&crop=entropy&q=90" "$SITE/fish-rohu.jpg"
|
||||
dl "https://images.unsplash.com/photo-1565680018434-b513d5e5fd47?auto=format&fit=crop&w=1400&q=90" "$SITE/fish-prawns.jpg"
|
||||
dl "https://images.unsplash.com/photo-1544551763-46a013bb70d5?auto=format&fit=crop&w=1400&h=900&crop=center&q=90" "$SITE/fish-basa.jpg"
|
||||
|
||||
echo "Fish images updated in $SITE"
|
||||
@@ -0,0 +1,127 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { ShieldCheck, Leaf, Award, Truck, Phone, Mail, MapPin } from 'lucide-react';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import {
|
||||
SITE_ADDRESS,
|
||||
SITE_EMAIL,
|
||||
SITE_PHONE_DISPLAY,
|
||||
SITE_HOURS,
|
||||
} from '@/lib/constants';
|
||||
|
||||
export default function AboutPage() {
|
||||
const { t } = useTranslation();
|
||||
const telHref = `tel:${SITE_PHONE_DISPLAY.replace(/\s/g, '')}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-gradient-to-br from-brand-900 to-brand-950 px-4 py-20 text-white sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<h1 className="mb-4 font-display text-4xl font-bold">
|
||||
{t('about.title', { name: t('site.name') })}
|
||||
</h1>
|
||||
<p className="text-lg text-brand-100">{t('about.subtitle')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-4xl px-4 py-16 sm:px-6 lg:px-8">
|
||||
<section className="mb-16">
|
||||
<h2 className="section-heading mb-4">{t('about.ourStory')}</h2>
|
||||
<p className="mb-4 leading-relaxed text-gray-600">
|
||||
{t('about.storyP1', { name: t('site.name') })}
|
||||
</p>
|
||||
<p className="leading-relaxed text-gray-600">{t('about.storyP2')}</p>
|
||||
</section>
|
||||
|
||||
<section id="halal" className="mb-16">
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<ShieldCheck className="h-8 w-8 text-brand-700" />
|
||||
<h2 className="section-heading">{t('about.halalTitle')}</h2>
|
||||
</div>
|
||||
<p className="leading-relaxed text-gray-600">
|
||||
{t('about.halalDesc', { name: t('site.name') })}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mb-16">
|
||||
<div className="grid gap-6 sm:grid-cols-3">
|
||||
{[
|
||||
{ icon: Leaf, title: t('about.freshDaily'), desc: t('about.freshDailyDesc') },
|
||||
{
|
||||
icon: Award,
|
||||
title: t('about.premiumQuality'),
|
||||
desc: t('about.premiumQualityDesc'),
|
||||
},
|
||||
{
|
||||
icon: Truck,
|
||||
title: t('about.fastDelivery'),
|
||||
desc: t('about.fastDeliveryDesc'),
|
||||
},
|
||||
].map((item) => (
|
||||
<div key={item.title} className="card-premium p-6 text-center">
|
||||
<item.icon className="mx-auto mb-3 h-8 w-8 text-brand-700" />
|
||||
<h3 className="mb-1 font-semibold text-brand-900">{item.title}</h3>
|
||||
<p className="text-sm text-gray-500">{item.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="delivery" className="mb-16">
|
||||
<h2 className="section-heading mb-4">{t('about.deliveryTitle')}</h2>
|
||||
<div className="space-y-3 text-gray-600">
|
||||
<p>{t('about.delivery1')}</p>
|
||||
<p>{t('about.delivery2')}</p>
|
||||
<p>{t('about.delivery3')}</p>
|
||||
<p>{t('about.delivery4')}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contact" className="card-premium mb-16 p-8">
|
||||
<h2 className="section-heading mb-6">{t('about.contactTitle')}</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 text-gray-600">
|
||||
<Phone className="h-5 w-5 text-brand-700" />
|
||||
<a href={telHref} className="hover:text-brand-800">
|
||||
{SITE_PHONE_DISPLAY}
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-gray-600">
|
||||
<Mail className="h-5 w-5 text-brand-700" />
|
||||
<a href={`mailto:${SITE_EMAIL}`} className="hover:text-brand-800">
|
||||
{SITE_EMAIL}
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 text-gray-600">
|
||||
<MapPin className="mt-0.5 h-5 w-5 shrink-0 text-brand-700" />
|
||||
<span>
|
||||
{SITE_ADDRESS}
|
||||
<br />
|
||||
{t('footer.hours', { hours: SITE_HOURS })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
href="/shop"
|
||||
className="text-sm font-semibold text-brand-700 hover:text-brand-900"
|
||||
>
|
||||
{t('cta.button')} →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="privacy" className="mb-16">
|
||||
<h2 className="section-heading mb-4">{t('about.privacyTitle')}</h2>
|
||||
<p className="leading-relaxed text-gray-600">{t('about.privacyText')}</p>
|
||||
</section>
|
||||
|
||||
<section id="terms" className="mb-8">
|
||||
<h2 className="section-heading mb-4">{t('about.termsTitle')}</h2>
|
||||
<p className="leading-relaxed text-gray-600">{t('about.termsText')}</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { User, Package, MapPin, Phone, Mail, LogOut, Heart } from 'lucide-react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { useAuth } from '@/presentation/hooks/useAuth';
|
||||
import { formatPrice, formatDate, getFormatLocale } from '@/lib/utils';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
|
||||
export default function AccountPage() {
|
||||
const router = useRouter();
|
||||
const { user, isAuthenticated, orders, logout } = useAuth();
|
||||
const { t, locale } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
router.push('/login');
|
||||
}
|
||||
}, [isAuthenticated, router]);
|
||||
|
||||
if (!isAuthenticated || !user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
const fmt = (n: number) => formatPrice(n, getFormatLocale(locale));
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50">
|
||||
<div className="border-b border-gray-100 bg-white">
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
<h1 className="section-heading">{t('account.title')}</h1>
|
||||
<p className="text-gray-500">{t('account.welcome', { name: user.name })}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-8 lg:grid-cols-3">
|
||||
<div className="space-y-6">
|
||||
<div className="card-premium p-6">
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-brand-100">
|
||||
<User className="h-6 w-6 text-brand-700" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-display text-lg font-semibold">{user.name}</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
{t('account.memberSince', { date: formatDate(user.createdAt) })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 border-t border-gray-100 pt-4">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Mail className="h-4 w-4 text-brand-600" />
|
||||
{user.email}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Phone className="h-4 w-4 text-brand-600" />
|
||||
{user.phone}
|
||||
</div>
|
||||
<div className="flex items-start gap-2 text-sm text-gray-600">
|
||||
<MapPin className="mt-0.5 h-4 w-4 shrink-0 text-brand-600" />
|
||||
{user.address.street}, {user.address.city}, {user.address.state}{' '}
|
||||
{user.address.zip}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-premium p-4">
|
||||
<Link
|
||||
href="/wishlist"
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-brand-50 hover:text-brand-700"
|
||||
>
|
||||
<Heart className="h-4 w-4" />
|
||||
{t('account.myWishlist')}
|
||||
</Link>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-red-500 transition-colors hover:bg-red-50"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
{t('account.signOut')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-2" id="orders">
|
||||
<div className="card-premium p-6">
|
||||
<div className="mb-6 flex items-center gap-2">
|
||||
<Package className="h-5 w-5 text-brand-700" />
|
||||
<h2 className="font-display text-lg font-semibold">
|
||||
{t('account.orderHistory')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{orders.length === 0 ? (
|
||||
<div className="py-12 text-center">
|
||||
<p className="mb-2 text-gray-500">{t('account.noOrders')}</p>
|
||||
<Link href="/shop">
|
||||
<Button variant="primary">{t('account.startShopping')}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{orders.map((order) => (
|
||||
<div
|
||||
key={order.id}
|
||||
className="rounded-xl border border-gray-100 p-4 transition-colors hover:border-brand-200"
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-brand-900">{order.id}</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{formatDate(order.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-end">
|
||||
<p className="font-bold text-brand-800">{fmt(order.total)}</p>
|
||||
<span className="inline-block rounded-full bg-brand-50 px-2 py-0.5 text-xs font-medium capitalize text-brand-700">
|
||||
{t(`orderStatus.${order.status}`)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{order.items.map((item) => {
|
||||
const localized = localizeProduct(item.product, t);
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className="flex justify-between text-sm text-gray-600"
|
||||
>
|
||||
<Link
|
||||
href={`/product/${item.product.slug}`}
|
||||
className="hover:text-brand-700"
|
||||
>
|
||||
{localized.name} × {item.quantity}
|
||||
<span className="ms-2 text-xs text-gold-600">
|
||||
({item.customizationLabel})
|
||||
</span>
|
||||
</Link>
|
||||
<span>{fmt(item.product.price * item.quantity)}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
'use client';
|
||||
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
import Link from 'next/link';
|
||||
import { Minus, Plus, Trash2, ShoppingBag, ArrowRight } from 'lucide-react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { useCart } from '@/presentation/hooks/useCart';
|
||||
import { formatPrice, getFormatLocale } from '@/lib/utils';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
|
||||
export default function CartPage() {
|
||||
const { items, updateQuantity, removeItem, summary } = useCart();
|
||||
const { t, locale } = useTranslation();
|
||||
const { subtotal: total, deliveryFee, grandTotal } = summary;
|
||||
const fmt = (n: number) => formatPrice(n, getFormatLocale(locale));
|
||||
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-20 text-center sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-brand-50">
|
||||
<ShoppingBag className="h-10 w-10 text-brand-300" />
|
||||
</div>
|
||||
<h1 className="mb-2 font-display text-2xl font-bold text-brand-900">
|
||||
{t('cart.empty')}
|
||||
</h1>
|
||||
<p className="mb-8 text-gray-500">{t('cart.emptyHint')}</p>
|
||||
<Link href="/shop">
|
||||
<Button variant="primary" size="lg">
|
||||
{t('cart.startShopping')}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50">
|
||||
<div className="border-b border-gray-100 bg-white">
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
<h1 className="section-heading">{t('cart.title')}</h1>
|
||||
<p className="text-gray-500">
|
||||
{t('cart.itemsCount', { count: items.length })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-8 lg:grid-cols-3">
|
||||
<div className="space-y-4 lg:col-span-2">
|
||||
{items.map((item) => {
|
||||
const localized = localizeProduct(item.product, t);
|
||||
return (
|
||||
<div key={item.id} className="card-premium flex gap-4 p-4 sm:p-6">
|
||||
<div className="relative h-24 w-24 shrink-0 overflow-hidden rounded-xl sm:h-28 sm:w-28">
|
||||
<AppImage
|
||||
src={item.product.image}
|
||||
alt={localized.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="(max-width: 640px) 96px, 112px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<Link
|
||||
href={`/product/${item.product.slug}`}
|
||||
className="font-display text-lg font-semibold text-brand-900 hover:text-brand-700"
|
||||
>
|
||||
{localized.name}
|
||||
</Link>
|
||||
<p className="mt-1 text-xs uppercase tracking-wider text-brand-600">
|
||||
{t(`categories.${item.product.category}.name`)}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => removeItem(item.id)}
|
||||
className="rounded-lg p-2 text-gray-400 transition-colors hover:bg-red-50 hover:text-red-500"
|
||||
aria-label={t('cart.removeItem')}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 inline-flex items-center gap-1.5 rounded-full bg-gold-50 px-3 py-1">
|
||||
<span className="text-xs font-medium text-gold-700">
|
||||
{t('cart.customization')}
|
||||
</span>
|
||||
<span className="text-xs font-semibold text-gold-800">
|
||||
{item.customizationLabel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-auto flex items-center justify-between pt-3">
|
||||
<div className="flex items-center rounded-lg border border-gray-200">
|
||||
<button
|
||||
onClick={() => updateQuantity(item.id, item.quantity - 1)}
|
||||
className="flex h-9 w-9 items-center justify-center text-gray-500 hover:text-brand-700"
|
||||
aria-label={t('product.decreaseQty')}
|
||||
>
|
||||
<Minus className="h-3 w-3" />
|
||||
</button>
|
||||
<span className="w-8 text-center text-sm font-semibold">
|
||||
{item.quantity}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => updateQuantity(item.id, item.quantity + 1)}
|
||||
className="flex h-9 w-9 items-center justify-center text-gray-500 hover:text-brand-700"
|
||||
aria-label={t('product.increaseQty')}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-lg font-bold text-brand-800">
|
||||
{fmt(item.product.price * item.quantity)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="card-premium sticky top-24 p-6">
|
||||
<h2 className="mb-4 font-display text-lg font-semibold text-brand-900">
|
||||
{t('cart.orderSummary')}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-3 border-b border-gray-100 pb-4">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">{t('cart.subtotal')}</span>
|
||||
<span className="font-medium">{fmt(total)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">{t('cart.delivery')}</span>
|
||||
<span className="font-medium">
|
||||
{deliveryFee === 0 ? (
|
||||
<span className="text-brand-700">{t('cart.free')}</span>
|
||||
) : (
|
||||
fmt(deliveryFee)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{deliveryFee > 0 && (
|
||||
<p className="text-xs text-gray-400">{t('cart.freeDeliveryHint')}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between py-4">
|
||||
<span className="font-semibold text-brand-900">{t('cart.total')}</span>
|
||||
<span className="text-xl font-bold text-brand-800">{fmt(grandTotal)}</span>
|
||||
</div>
|
||||
|
||||
<Link href="/checkout">
|
||||
<Button variant="gold" size="lg" className="w-full">
|
||||
{t('cart.proceedCheckout')}
|
||||
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/shop"
|
||||
className="mt-3 block text-center text-sm font-medium text-brand-600 hover:text-brand-800"
|
||||
>
|
||||
{t('cart.continueShopping')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import AppImage from '@/components/ui/AppImage';
|
||||
import Link from 'next/link';
|
||||
import { Lock, CreditCard, Truck, CheckCircle, ChevronLeft } from 'lucide-react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { useCart } from '@/presentation/hooks/useCart';
|
||||
import { useAuth } from '@/presentation/hooks/useAuth';
|
||||
import { useCheckout } from '@/presentation/hooks/useCheckout';
|
||||
import { formatPrice, getFormatLocale } from '@/lib/utils';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { localizeProduct } from '@/lib/product-i18n';
|
||||
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const router = useRouter();
|
||||
const { items, summary } = useCart();
|
||||
const { user, isAuthenticated } = useAuth();
|
||||
const { placeOrder } = useCheckout();
|
||||
const { t, locale } = useTranslation();
|
||||
|
||||
const [paymentMethod, setPaymentMethod] = useState('card');
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [orderComplete, setOrderComplete] = useState(false);
|
||||
const [orderId, setOrderId] = useState('');
|
||||
|
||||
const [form, setForm] = useState({
|
||||
name: user?.name || '',
|
||||
email: user?.email || '',
|
||||
phone: user?.phone || '',
|
||||
street: user?.address.street || '',
|
||||
city: user?.address.city || '',
|
||||
state: user?.address.state || '',
|
||||
zip: user?.address.zip || '',
|
||||
cardNumber: '',
|
||||
expiry: '',
|
||||
cvv: '',
|
||||
});
|
||||
|
||||
const { subtotal: total, deliveryFee, grandTotal } = summary;
|
||||
const fmt = (n: number) => formatPrice(n, getFormatLocale(locale));
|
||||
|
||||
if (items.length === 0 && !orderComplete) {
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-20 text-center">
|
||||
<h1 className="mb-4 font-display text-2xl font-bold">{t('checkout.noItems')}</h1>
|
||||
<Link href="/shop">
|
||||
<Button>{t('checkout.goToShop')}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (orderComplete) {
|
||||
return (
|
||||
<div className="mx-auto max-w-lg px-4 py-20 text-center">
|
||||
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-brand-50">
|
||||
<CheckCircle className="h-12 w-12 text-brand-700" />
|
||||
</div>
|
||||
<h1 className="mb-2 font-display text-3xl font-bold text-brand-900">
|
||||
{t('checkout.orderConfirmed')}
|
||||
</h1>
|
||||
<p className="mb-2 text-gray-500">{t('checkout.thankYou')}</p>
|
||||
<p className="mb-8 text-sm font-medium text-brand-700">
|
||||
{t('checkout.orderId', { id: orderId })}
|
||||
</p>
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:justify-center">
|
||||
<Link href="/account">
|
||||
<Button variant="primary">{t('checkout.viewOrders')}</Button>
|
||||
</Link>
|
||||
<Link href="/shop">
|
||||
<Button variant="secondary">{t('cart.continueShopping')}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsProcessing(true);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
const { order } = placeOrder({
|
||||
deliveryAddress: {
|
||||
street: form.street,
|
||||
city: form.city,
|
||||
state: form.state,
|
||||
zip: form.zip,
|
||||
},
|
||||
paymentMethod,
|
||||
});
|
||||
|
||||
setOrderId(order.id);
|
||||
setOrderComplete(true);
|
||||
setIsProcessing(false);
|
||||
};
|
||||
|
||||
const updateField = (field: string, value: string) => {
|
||||
setForm((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50">
|
||||
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<Link
|
||||
href="/cart"
|
||||
className="mb-6 inline-flex items-center gap-1 text-sm font-medium text-gray-500 hover:text-brand-700"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4 rtl:rotate-180" />
|
||||
{t('checkout.backToCart')}
|
||||
</Link>
|
||||
|
||||
<h1 className="section-heading mb-8">{t('checkout.title')}</h1>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-8 lg:grid-cols-3">
|
||||
<div className="space-y-6 lg:col-span-2">
|
||||
{!isAuthenticated && (
|
||||
<div className="card-premium p-6">
|
||||
<p className="text-sm text-gray-600">
|
||||
{t('checkout.haveAccount')}{' '}
|
||||
<Link href="/login" className="font-semibold text-brand-700 hover:underline">
|
||||
{t('checkout.signIn')}
|
||||
</Link>{' '}
|
||||
{t('checkout.fasterCheckout')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="card-premium p-6">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<Truck className="h-5 w-5 text-brand-700" />
|
||||
<h2 className="font-display text-lg font-semibold">
|
||||
{t('checkout.deliveryDetails')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="sm:col-span-2">
|
||||
<label className="label-text">{t('checkout.fullName')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
value={form.name}
|
||||
onChange={(e) => updateField('name', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-text">{t('checkout.email')}</label>
|
||||
<input
|
||||
required
|
||||
type="email"
|
||||
className="input-field"
|
||||
value={form.email}
|
||||
onChange={(e) => updateField('email', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-text">{t('checkout.phone')}</label>
|
||||
<input
|
||||
required
|
||||
type="tel"
|
||||
className="input-field"
|
||||
value={form.phone}
|
||||
onChange={(e) => updateField('phone', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label className="label-text">{t('checkout.street')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
value={form.street}
|
||||
onChange={(e) => updateField('street', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-text">{t('checkout.city')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
value={form.city}
|
||||
onChange={(e) => updateField('city', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-text">{t('checkout.state')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
value={form.state}
|
||||
onChange={(e) => updateField('state', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-text">{t('checkout.zip')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
value={form.zip}
|
||||
onChange={(e) => updateField('zip', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-premium p-6">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<CreditCard className="h-5 w-5 text-brand-700" />
|
||||
<h2 className="font-display text-lg font-semibold">{t('checkout.payment')}</h2>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex gap-3">
|
||||
{['card', 'cod'].map((method) => (
|
||||
<button
|
||||
key={method}
|
||||
type="button"
|
||||
onClick={() => setPaymentMethod(method)}
|
||||
className={`rounded-lg border-2 px-4 py-2 text-sm font-medium transition-all ${
|
||||
paymentMethod === method
|
||||
? 'border-brand-700 bg-brand-700 text-white'
|
||||
: 'border-gray-200 text-gray-600 hover:border-brand-300'
|
||||
}`}
|
||||
>
|
||||
{method === 'card'
|
||||
? t('checkout.creditCard')
|
||||
: t('checkout.cashOnDelivery')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{paymentMethod === 'card' && (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="sm:col-span-2">
|
||||
<label className="label-text">{t('checkout.cardNumber')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
placeholder="1234 5678 9012 3456"
|
||||
value={form.cardNumber}
|
||||
onChange={(e) => updateField('cardNumber', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-text">{t('checkout.expiry')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
placeholder="MM/YY"
|
||||
value={form.expiry}
|
||||
onChange={(e) => updateField('expiry', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-text">{t('checkout.cvv')}</label>
|
||||
<input
|
||||
required
|
||||
className="input-field"
|
||||
placeholder="123"
|
||||
value={form.cvv}
|
||||
onChange={(e) => updateField('cvv', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="card-premium sticky top-24 p-6">
|
||||
<h2 className="mb-4 font-display text-lg font-semibold">
|
||||
{t('cart.orderSummary')}
|
||||
</h2>
|
||||
|
||||
<div className="mb-4 max-h-60 space-y-3 overflow-y-auto">
|
||||
{items.map((item) => {
|
||||
const localized = localizeProduct(item.product, t);
|
||||
return (
|
||||
<div key={item.id} className="flex gap-3">
|
||||
<div className="relative h-12 w-12 shrink-0 overflow-hidden rounded-lg">
|
||||
<AppImage
|
||||
src={item.product.image}
|
||||
alt={localized.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="48px"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-brand-900">
|
||||
{localized.name}
|
||||
</p>
|
||||
<p className="text-xs text-gold-700">
|
||||
{item.customizationLabel}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{t('checkout.qty', { count: item.quantity })}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
{fmt(item.product.price * item.quantity)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 border-t border-gray-100 pt-4">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">{t('cart.subtotal')}</span>
|
||||
<span>{fmt(total)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">{t('cart.delivery')}</span>
|
||||
<span>
|
||||
{deliveryFee === 0 ? t('cart.free') : fmt(deliveryFee)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between pt-2 text-lg font-bold">
|
||||
<span>{t('cart.total')}</span>
|
||||
<span className="text-brand-800">{fmt(grandTotal)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="gold"
|
||||
size="lg"
|
||||
className="mt-6 w-full"
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Lock className="h-4 w-4" />
|
||||
{isProcessing
|
||||
? t('checkout.processing')
|
||||
: t('checkout.pay', { amount: fmt(grandTotal) })}
|
||||
</Button>
|
||||
|
||||
<p className="mt-3 flex items-center justify-center gap-1 text-xs text-gray-400">
|
||||
<Lock className="h-3 w-3" />
|
||||
{t('checkout.secure')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||