E-Ticaret Projesi — Şu Ana Kadar Yapılanlar
Bugünkü durum: Çalışan bir Next.js 15 e-ticaret iskeleti — gerçek veriye bağlı katalog, fonksiyonel sepet, sipariş oluşturma. Tek eksik: ödeme entegrasyonu (iyzico, C adımı).
1. Adım — Next.js iskeleti + GTM tracking altyapısı
Ne kuruldu:
| Bileşen | Sürüm / Detay |
|---|---|
| Framework | Next.js 15.5.18 (App Router, React 19) |
| Dil | TypeScript 5.7 |
| Styling | Tailwind v4 |
| UI Kit | Shadcn UI — Button/Input/Card (manuel, npx ESM hatası nedeniyle CLI atlandı) |
| Paket yöneticisi | pnpm 11.1.2 |
| Klasör yapısı | src/ (app/, components/{ui,common,product,cart,checkout}, lib/, hooks/) |
KRAL KURAL altyapısı kuruldu:
src/lib/analytics/gtm.ts—initDataLayer(),pushToDataLayer()(try-catch sarmalı, hata uygulamayı kırmaz)src/lib/analytics/events.ts—trackViewItem,trackAddToCart,trackBeginCheckout,trackPurchasehelper'ları + GA4 best practiceecommerce: nullresetsrc/lib/analytics/types.ts— GA4 Enhanced Ecommerce tiplerisrc/components/common/GTMScript.tsx— Container script + noscript iframeapp/layout.tsx— Türkçe<html lang="tr">, metadata, GTM yerleştirmeapp/providers.tsx— Client provider katmanı, mount'ta dataLayer init
2. Adım — Prisma + PostgreSQL şeması
Veritabanı: Neon Postgres (Frankfurt) — pooled + direct URL ayrımı (pgBouncer)
ORM: Prisma 6.19.3 (v7'deki breaking change'ler nedeniyle v6'da kaldı)
prisma/schema.prisma — 14 model:
| Domain | Model'ler |
|---|---|
| Kullanıcı | User, Address (billing/shipping) |
| Katalog | Category (nested tree), Product, ProductVariant, Inventory, ProductImage |
| Sepet | Cart (guest + user, sessionId), CartItem |
| Sipariş | Order (status state machine), OrderItem (variant snapshot), Payment |
| Pazarlama | Coupon (PERCENT/FIXED), Review (moderasyonlu) |
Tasarım kararları:
- Tüm tutarlar
Decimal(12,2)(float yuvarlama hatası önlemek için) OrderItemvariant snapshot içerir — variant silinse bile sipariş bozulmazcustomerEmailOrder'da ayrıca tutulur (Meta CAPI hash'lemesi için)
Seed verisi: 3 kategori (Elektronik / Giyim / Ev & Yaşam), 10 ürün, 23 variant, 2 kupon (HOSGELDIN10, KARGOFREE). Idempotent.
db scripts (package.json):
pnpm db:generate / db:push / db:migrate / db:seed / db:reset / db:studio
Hepsi dotenv-cli -e .env.local -- prefix'i ile çalışır (Prisma CLI .env.local okumadığı için).
3. Adım — Katalog sayfaları (KRAL KURAL'ın ilk gerçek event'leri)
Server-side query katmanı — src/lib/queries/products.ts (server-only):
getFeaturedProducts(limit),getProductsByCategorySlug(slug),getProductBySlug(slug),getAllCategories(),getAllProductSlugs()- Decimal → number serialization burada (Server → Client Component prop güvenliği)
Sayfalar:
| Route | Tip | Ne yapar |
|---|---|---|
/ | SSG (1h ISR) | Featured ürünler + kategori chip'leri |
/kategori/[slug] | SSG, generateStaticParams | 3 kategori pre-render |
/urun/[slug] | SSG, generateStaticParams | 10 ürün pre-render, breadcrumb, gallery, variant selector |
Components:
ProductCard+ProductGrid(Server) — fiyat aralığı (min – max), kategori, markaProductGallery(Client) — ana görsel + thumbnail navigationProductPurchaseBox(Client) — KRAL KURAL'ın ilk gerçek uygulaması: - Mount'taview_itemevent (1 kez) - Variant seçimine göre fiyat/stok dinamik güncelleme - Stok ≤ 5 ise "Son N adet!" uyarısı
Header Server Component'e çevrildi — kategorileri Prisma'dan canlı çekiyor.
A Adımı — Sepet (Cart Context + localStorage)
src/lib/cart/store.tsx — domain'in kalbi:
CartProvider+useCart()hook- Reducer pattern (
add/setQuantity/remove/clear/hydrate) - localStorage key:
cart:v1(versioned — gelecek migration'lar için) - Hydration-safe: SSR'da
isHydrated=false, mount sonrası storage'tan yüklenir cartLineToGA4Item()— sepet satırını GA4 item formatına çevirir (begin_checkout / purchase için reuse edilecek)
Components:
CartBadge— sepet ikonunun sayaç badge'i (SSR'danull→ hydration mismatch yok)CartLineRow— qty +/-, sil, satır toplamıCartSummary— ara toplam + "Ödemeye geç" CTACartContents— skeleton → boş state → satırlar+özet 3-path orchestrator
/sepet — CartContents kullanıyor, "Sepetin boş" empty state'i var.
ProductPurchaseBox entegrasyonu:
addLine()çağrısı +add_to_cartevent'i her tıklamadamaxQuantity=stock— sepete eklerken stok aşımı engelleniyor
Güvenlikler:
- localStorage hata → console.warn, uygulama çalışmaya devam
- Negatif quantity → satırı kaldırır
- Stok aşımı → clamp
- Versioned key → gelecek schema değişiklikleri için migration kapısı
B Adımı — Checkout + begin_checkout event + Order create
Form altyapısı: react-hook-form 7.76 + zod 4.4 + @hookform/resolvers
src/lib/checkout/schema.ts — Zod schemas (client + server tek source of truth):
checkoutSchema— e-posta, ad-soyad, telefon (TR 10-11 hane regex), line1/line2, şehir, ilçe, posta kodu (5 hane), notcartLineSnapshotSchema— client → server geçişicreateOrderInputSchema— toplu validation
src/app/actions/checkout.ts — Server Action createOrder:
- Zod parse (defansif)
- Fiyat & stok DB re-validation — client snapshot'a güvenilmiyor
User.upsert(email-based guest checkout)Address.create(SHIPPING)Order + OrderItemstransaction- Order number retry —
YYYY-XXXXXXbase36, P2002 unique violation'da 3 kez retry
src/lib/queries/orders.ts — getOrderByNumber(orderNumber) confirmation page için.
UI:
| Component | Sorumluluk |
|---|---|
CheckoutForm | RHF + zodResolver, TR autoComplete attribute'ları, inline hata mesajları |
CheckoutSummary | Sticky sidebar, sepet item'ları + ara toplam |
CheckoutClient | Boş sepet → /sepet redirect, begin_checkout (useRef ile bir kez), submit handler |
Sayfalar:
/odeme—CheckoutClientrender eder/siparis/[orderNumber]— Server,force-dynamic,robots: noindex, "Siparişin alındı" + items + adres + status badge
Akış:
/sepet → /odeme → mount'ta begin_checkout event
→ form submit → createOrder server action
→ Order PENDING create
→ cart.clear() + localStorage temizle
→ router.push(/siparis/2026-XXXXXX)
E2E test sonucu (Neon'a karşı): Order 2026-SROXR2 başarıyla oluştu, items + address join çalışıyor, confirmation sayfası canlı render ediyor.
Mevcut Stack — Hızlı Referans
| Katman | Teknoloji |
|---|---|
| Framework | Next.js 15.5.18 + React 19 + TypeScript 5.7 |
| Style | Tailwind v4 |
| UI | Shadcn UI (Button, Input, Card — manuel) + lucide-react ikonlar |
| State (cart) | React Context + Reducer + localStorage |
| Form | react-hook-form + zod |
| ORM | Prisma 6.19.3 |
| DB | Neon Postgres (Frankfurt, pooled + direct URL) |
| Tracking | GTM + GA4 (dataLayer), Meta CAPI altyapısı hazır (C'de devreye girecek) |
| Paket yöneticisi | pnpm 11.1.2 |
KRAL KURAL Durumu
| Event | Durum | Nerede tetikleniyor |
|---|---|---|
view_item | ✅ Çalışıyor | ProductPurchaseBox mount, ilk variant ile |
add_to_cart | ✅ Çalışıyor | "Sepete ekle" tıklama, seçili variant ile |
begin_checkout | ✅ Çalışıyor | /odeme mount, tüm sepet item'ları ile (1 kez, ref guard) |
purchase | ⏳ C adımı | iyzico webhook → server (Meta CAPI) + client (/siparis?paid=1 query) eşzamanlı |
Dosya Haritası (özet)
src/
├── app/
│ ├── (shop)/
│ │ ├── page.tsx # Anasayfa, featured + kategori chip'leri
│ │ ├── kategori/[slug]/page.tsx # Kategori listesi
│ │ ├── urun/[slug]/page.tsx # Ürün detay
│ │ ├── sepet/page.tsx # Sepet
│ │ ├── odeme/page.tsx # Checkout
│ │ └── siparis/[orderNumber]/page.tsx # Onay
│ ├── actions/
│ │ └── checkout.ts # createOrder server action
│ ├── layout.tsx # GTM script + metadata
│ └── providers.tsx # CartProvider + dataLayer init
│
├── components/
│ ├── common/{Header,Footer,GTMScript}.tsx
│ ├── product/{ProductCard,ProductGallery,ProductPurchaseBox}.tsx
│ ├── cart/{CartBadge,CartLineRow,CartSummary,CartContents}.tsx
│ ├── checkout/{CheckoutClient,CheckoutForm,CheckoutSummary}.tsx
│ └── ui/{button,input,card}.tsx # Shadcn primitives
│
├── lib/
│ ├── analytics/{gtm,events,types}.ts # KRAL KURAL altyapısı
│ ├── cart/store.tsx # CartProvider + useCart
│ ├── checkout/schema.ts # Zod schemas
│ ├── queries/{products,orders}.ts # server-only Prisma queries
│ ├── server/orderNumber.ts # YYYY-XXXXXX generator
│ ├── db.ts # Prisma singleton
│ ├── constants.ts # SITE_NAME, GTM_ID, DEFAULT_CURRENCY
│ ├── format.ts # formatCurrency(value, "TRY")
│ └── utils.ts # cn() helper
│
└── prisma/
├── schema.prisma # 14 model
├── seed.ts # 3 kat + 10 ürün + 23 variant
└── migrations/20260520134448_init/
