# 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/`