Customer Portal CRM — Design Specification
DerinDate: 2026-03-27 Status: Approved Scope: Phase 1 — Customer-facing portal + admin management
1. Overview
Bölüm başlığı “1. Overview”A customer-facing CRM portal for ECU Tuning Portal’s B2B SaaS white-label software business. Customers log in to view their products, services, files, invoices, development requests, and API information. Admin manages everything via CRUD operations.
Core Principles
Bölüm başlığı “Core Principles”- Monolithic — built into the existing Next.js 16 app, extending current Prisma/i18n/Tailwind stack
- Admin-driven — no automation, no self-registration, admin controls everything via CRUD
- Zero cross-access — admin and customer auth systems are physically isolated
- No over-engineering — simplest working solution, CRUD-first, automate later
2. Authentication Architecture
Bölüm başlığı “2. Authentication Architecture”Admin Auth (Existing — No Changes)
Bölüm başlığı “Admin Auth (Existing — No Changes)”- Library: NextAuth v5 (Auth.js) — Credentials provider
- Cookie:
authjs.session-token(encrypted JWT) - Table:
AdminUser - Login:
/admin/login
Customer Auth (New)
Bölüm başlığı “Customer Auth (New)”- Library: iron-session v8
- Cookie:
customer-session(encrypted via @hapi/iron — signed + encrypted, not JWT) - Tables:
Customer+CustomerSession - Login:
/customer/login - Session flow:
- Customer submits email + password to
/customer/login - Server Action: lookup
Customertable, bcrypt password verification - Create
CustomerSessionrecord in DB - iron-session encrypts
{sessionId, customerId, role: "customer"}into cookie - On each request: decrypt cookie → verify
CustomerSessionexists in DB → checkisActive - Logout: delete
CustomerSessionfrom DB + destroy iron-session cookie
- Customer submits email + password to
Session State Management
Bölüm başlığı “Session State Management”- Server Components: Direct iron-session access via
getIronSession(await cookies(), config) - Server Actions: Same — direct access
- Client Components: React Context —
<CustomerSessionProvider>wraps portal layout,useCustomerSession()hook - No external state library needed — session data is read-mostly, React Context is sufficient
Three-Layer Security Isolation
Bölüm başlığı “Three-Layer Security Isolation”| Layer | Scope | Mechanism |
|---|---|---|
| Proxy (middleware) | Optimistic redirect | Cookie presence + role check → redirect wrong role to correct login |
| Server Component | Page-level auth | getIronSession() + role verification → redirect if unauthorized |
| Server Action | Per-mutation guard | Session + role + customerId ownership check → reject unauthorized data access |
File Download Security (Zero Tolerance)
Bölüm başlığı “File Download Security (Zero Tolerance)”- Files stored outside
public/— no direct URL access - Download only via
/api/customer/files/[id]/download - 4-step verification: session exists → role is customer → file belongs to customer → account is active
- File streamed (not loaded into memory)
- File path never exposed in URLs
- All downloads logged (who, when, which file)
Cookie Isolation
Bölüm başlığı “Cookie Isolation”- Two different cookies (
authjs.session-tokenvscustomer-session) - Admin and customer can be logged in simultaneously in the same browser
- No shared session state, no shared auth logic
- Zero changes to existing NextAuth admin configuration
3. Database Schema
Bölüm başlığı “3. Database Schema”New Tables
Bölüm başlığı “New Tables”Customer
Bölüm başlığı “Customer”model Customer { id Int @id @default(autoincrement()) email String @unique password String // bcrypt hash name String company String? phone String? locale String @default("tr") isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
sessions CustomerSession[] services Service[] files File[] invoices Invoice[] developmentRequests DevelopmentRequest[]}CustomerSession
Bölüm başlığı “CustomerSession”model CustomerSession { id Int @id @default(autoincrement()) customerId Int sessionToken String @unique ipAddress String? userAgent String? expiresAt DateTime createdAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)}ServiceType
Bölüm başlığı “ServiceType”model ServiceType { id Int @id @default(autoincrement()) slug String @unique name String description String? createdAt DateTime @default(now())
services Service[]}Seed data: license, hosting, domain, api, development
Service
Bölüm başlığı “Service”model Service { id Int @id @default(autoincrement()) customerId Int serviceTypeId Int name String // e.g., "Enterprise License — tuningshop.de" description String? status String @default("active") // active | expired | suspended | cancelled startDate DateTime? endDate DateTime? autoRenew Boolean @default(false) price Decimal? @db.Decimal(10, 2) currency String @default("EUR") billingCycle String? // monthly | yearly | one-time metadata Json? // flexible type-specific data (serverIp, ram, disk, registrar, apiKey, rateLimit, etc.) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) serviceType ServiceType @relation(fields: [serviceTypeId], references: [id]) files File[] developmentRequests DevelopmentRequest[]}model File { id Int @id @default(autoincrement()) customerId Int serviceId Int? // optional — can be general or service-specific fileName String // stored filename (UUID-based) originalName String // original upload filename filePath String // server path (outside public/) fileSize BigInt mimeType String version String // e.g., "2.4.1" changelog String? // version release notes uploadedBy Int // FK → AdminUser createdAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) service Service? @relation(fields: [serviceId], references: [id]) admin AdminUser @relation(fields: [uploadedBy], references: [id])}Invoice
Bölüm başlığı “Invoice”model Invoice { id Int @id @default(autoincrement()) customerId Int invoiceNumber String @unique // e.g., "INV-2026-001" title String description String? amount Decimal @db.Decimal(10, 2) currency String @default("EUR") status String @default("pending") // paid | pending | overdue | cancelled items Json // array of line items: [{description, quantity, unitPrice, total}] paidAt DateTime? dueDate DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)}DevelopmentRequest
Bölüm başlığı “DevelopmentRequest”model DevelopmentRequest { id Int @id @default(autoincrement()) customerId Int serviceId Int? // optional link to a service title String description String status String @default("pending") // pending | in_progress | completed | cancelled priority String @default("medium") // low | medium | high estimatedPrice Decimal? @db.Decimal(10, 2) finalPrice Decimal? @db.Decimal(10, 2) completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) service Service? @relation(fields: [serviceId], references: [id])}ApiChangelog (Global — visible to all customers)
Bölüm başlığı “ApiChangelog (Global — visible to all customers)”model ApiChangelog { id Int @id @default(autoincrement()) version String title String content String // markdown publishedAt DateTime createdAt DateTime @default(now())}Schema Design Decisions
Bölüm başlığı “Schema Design Decisions”metadataJson field on Service — stores type-specific data (serverIp, ram, disk for hosting; registrar, nameservers for domain; apiKey, rateLimit for API). No schema migration needed when adding new service types.itemsJson field on Invoice — line items array. Supports mixed invoices (multiple services + extras in one invoice).File.serviceIdoptional — file can be general (e.g., source code delivery) or service-specific.ServiceTypeas separate table — admin can CRUD new types without code changes.AdminUserrelation on File — tracks who uploaded each file.
4. Route Architecture
Bölüm başlığı “4. Route Architecture”Customer Portal Routes
Bölüm başlığı “Customer Portal Routes”app/[locale]/customer/├── login/page.tsx ← Public├── forgot-password/page.tsx ← Public├── (portal)/ ← Auth-protected (iron-session)│ ├── layout.tsx ← Session check + Sidebar + Header + CustomerSessionProvider│ ├── dashboard/page.tsx│ ├── services/│ │ ├── page.tsx ← All services (filterable)│ │ └── [id]/page.tsx ← Service detail│ ├── files/│ │ ├── page.tsx ← Files list (versioned)│ │ └── [id]/page.tsx ← File detail + download│ ├── development-requests/│ │ ├── page.tsx ← Request list│ │ └── [id]/page.tsx ← Request detail│ ├── invoices/│ │ ├── page.tsx ← Invoice list│ │ └── [id]/page.tsx ← Invoice detail│ ├── api-docs/│ │ └── page.tsx ← API Keys + Changelog│ └── profile/│ └── page.tsx ← Profile + password changeAdmin Panel Additions
Bölüm başlığı “Admin Panel Additions”app/[locale]/admin/(panel)/├── customers/│ ├── page.tsx ← Customer list│ ├── create/page.tsx ← Create customer│ └── [id]/│ ├── edit/page.tsx ← Edit customer│ └── services/page.tsx ← Customer's services├── services/│ ├── page.tsx ← All services (admin view)│ ├── [id]/edit/page.tsx ← Edit service│ └── types/page.tsx ← Service types CRUD├── files/│ ├── page.tsx ← File management│ └── upload/page.tsx ← Upload file (assign to customer)├── invoices/│ ├── page.tsx ← All invoices│ ├── create/page.tsx ← Create invoice│ └── [id]/edit/page.tsx ← Edit invoice├── development-requests/│ ├── page.tsx ← Manage dev requests│ └── [id]/edit/page.tsx ← Edit dev request└── api-changelog/ ├── page.tsx ← Changelog list ├── create/page.tsx ← New changelog entry └── [id]/edit/page.tsx ← Edit changelog entryAPI Routes
Bölüm başlığı “API Routes”app/api/customer/├── auth/│ ├── login/route.ts ← Customer login│ └── logout/route.ts ← Customer logout├── files/│ └── [id]/download/route.ts ← Secure file download (stream)└── session/route.ts ← Session endpoint for client components5. Component Architecture
Bölüm başlığı “5. Component Architecture”New Components
Bölüm başlığı “New Components”components/├── customer/ ← Customer portal components│ ├── CustomerSidebar.tsx ← Glassmorphism sidebar (brand-red accent)│ ├── CustomerHeader.tsx ← Top bar (title, breadcrumb, lang)│ ├── CustomerLayoutWrapper.tsx ← Layout wrapper│ ├── DashboardStats.tsx ← Stat cards (4-column grid)│ ├── ServiceCard.tsx ← Service summary card (progress bar)│ ├── ServiceDetail.tsx ← Service detail view│ ├── FileList.tsx ← File list with versions│ ├── FileDownloadButton.tsx ← Secure download button│ ├── InvoiceTable.tsx ← Invoice table│ ├── DevRequestCard.tsx ← Dev request card│ ├── ApiKeyDisplay.tsx ← Masked API key display│ ├── ChangelogTimeline.tsx ← Changelog timeline│ └── ProfileForm.tsx ← Profile edit form├── admin/ ← New admin components│ ├── CustomerForm.tsx ← Create/edit customer│ ├── ServiceForm.tsx ← Create/edit service│ ├── FileUploadForm.tsx ← Upload file│ ├── InvoiceForm.tsx ← Create invoice│ └── ApiChangelogForm.tsx ← Changelog entry formShared Libraries
Bölüm başlığı “Shared Libraries”lib/├── customer-session.ts ← iron-session config + getCustomerSession() helper└── file-storage.ts ← Secure file upload/download utilities
contexts/└── CustomerSessionContext.tsx ← React Context + Provider + useCustomerSession() hook6. UI Design Language
Bölüm başlığı “6. UI Design Language”The customer portal follows the existing landing page design system exactly:
Color Palette
Bölüm başlığı “Color Palette”- Background:
#030304(near-black, same as Hero/Features sections) - Panel surfaces:
rgba(255,255,255,0.02)withbackdrop-blur-xl(glass effect) - Borders:
rgba(255,255,255,0.06)(matchesborder-white/5~border-white/10) - Primary accent:
#ef4444(brand-red) — active states, glows, stat highlights - Status colors: Green
#22c55e(active), Amber#f59e0b(warning), Blue#3b82f6(info) - Red glow orbs:
rgba(239,68,68,0.04–0.06)withblur(120px)— ambient depth
Typography
Bölüm başlığı “Typography”- Headings/Labels/Badges: Chakra Petch (
font-tech) — uppercase, tracking-wide - Body/Description: Inter (
font-sans) - Badge pattern:
font-tech uppercase tracking-[0.2em] text-xs
Card Patterns
Bölüm başlığı “Card Patterns”- Glass cards:
bg-white/2 backdrop-blur-xl border border-white/6 rounded-[10px] - Stat cards: Glass card + bottom accent gradient line (2px)
- Service cards: Glass card + progress bar + hover border-red animation
- Warning cards:
border-amber-500/25
Glassmorphism Sidebar
Bölüm başlığı “Glassmorphism Sidebar”- Background:
rgba(255,255,255,0.02)+backdrop-blur(20px) - Red glow orb in top-left corner
- Active nav item:
rgba(239,68,68,0.08)bg +rgba(239,68,68,0.15)border - User avatar: Red gradient (
#ef4444→#b91c1c) with glow shadow - Grouped navigation with uppercase labels
Background
Bölüm başlığı “Background”- 60px grid overlay (matching landing page pattern)
- Red + blue glow orbs for ambient depth
Dashboard Layout (Hybrid)
Bölüm başlığı “Dashboard Layout (Hybrid)”- Top: 4-column stat cards (active services count, upcoming renewals, open requests, last invoice)
- Middle: 2x2 service card grid with progress bars
- Bottom: 2-column — recent activity feed + upcoming dates
Note: All UI labels shown in the mockups are design-time placeholders. Implementation must use
next-intltranslation keys from theCustomerPortalnamespace — never hardcode strings.
7. Internationalization (i18n)
Bölüm başlığı “7. Internationalization (i18n)”- Uses existing
next-intlinfrastructure (24 locales configured) - Customer portal translation namespace:
CustomerPortal(separate from public site translations) - Phase 1 delivers: Turkish (
tr) + English (en) translations only - Customer’s preferred locale stored in
Customer.localefield - Portal respects customer’s locale preference (set by admin at creation, changeable in profile)
- All 24 locale translations can be added later without code changes
8. Phasing & Future Roadmap
Bölüm başlığı “8. Phasing & Future Roadmap”Phase 1 — Customer Portal CRM (This Spec)
Bölüm başlığı “Phase 1 — Customer Portal CRM (This Spec)”- Customer auth (iron-session) + database schema
- Admin CRUD: customers, services, service types, files, invoices, dev requests, API changelog
- Customer portal: dashboard, services, files (versioned + secure download), invoices, dev requests, API (keys + changelog), profile
- i18n infrastructure (24-locale ready, TR + EN delivered)
- Three-layer security isolation
Phase 2 — Admin Pricing Management
Bölüm başlığı “Phase 2 — Admin Pricing Management”- Landing page pricing page fully managed from admin panel
- Prices, features, descriptions, all copy — admin-controlled
- Multi-language content management
Phase 3 — Communication System
Bölüm başlığı “Phase 3 — Communication System”- Infobip + Brevo integration
- Email + SMS notifications
- Service expiry reminders, new file notifications
- Automated alerts
Phase 4 — Update & Payment Automation
Bölüm başlığı “Phase 4 — Update & Payment Automation”- Update package sales
- Stripe integration
- Automatic invoice generation
- Payment-gated file access control
9. Security Summary
Bölüm başlığı “9. Security Summary”| Concern | Mitigation |
|---|---|
| Cross-access (admin ↔ customer) | Physically separate auth libraries, separate cookies, 3-layer enforcement |
| File theft | Files outside public/, 4-step auth check on download, stream-based delivery, download logging |
| Session hijacking | iron-session encryption (@hapi/iron), DB-backed session validation, IP/UA tracking |
| Session revocation | Delete CustomerSession from DB → immediate invalidation |
| Brute force | Rate limiting on login endpoint (existing lib/rate-limit.ts in-memory limiter) |
| Data leakage | Every query filtered by customerId — customer can only see own data |
| Account deactivation | isActive check on every request — instant lockout |