Mecca digital • Codezila Portfolio
Case Study

Mecca digital

Complete reference for the **Mecca Digital** link-in-bio platform: what it is, how it is built, how to run it, and how each feature works end-to-end.

Mecca digital
Project Details

About This Project

# Mecca Digital — Project Guide

Complete reference for the **Mecca Digital** link-in-bio platform: what it is, how it is built, how to run it, and how each feature works end-to-end.

**Product name:** Mecca Digital  
**Repository layout:** Monorepo (`frankin-app` backend + `frontend` SPA)  
**Last updated:** 2026-08-02

---

## Table of Contents

1. [What Is This Project?](#1-what-is-this-project)
2. [Repository Structure](#2-repository-structure)
3. [Technology Stack](#3-technology-stack)
4. [Architecture Overview](#4-architecture-overview)
5. [How It Works (End-to-End)](#5-how-it-works-end-to-end)
6. [Database Model](#6-database-model)
7. [Backend API Reference](#7-backend-api-reference)
8. [Frontend Routes & Pages](#8-frontend-routes--pages)
9. [Feature Deep Dives](#9-feature-deep-dives)
10. [Local Development Setup](#10-local-development-setup)
11. [Docker & Services](#11-docker--services)
12. [Environment Variables](#12-environment-variables)
13. [Testing](#13-testing)
14. [Demo Account & Seed Data](#14-demo-account--seed-data)
15. [Key Source Files](#15-key-source-files)
16. [Implementation Status](#16-implementation-status)
17. [Troubleshooting](#17-troubleshooting)
18. [Related Documents](#18-related-documents)

---

## 1. What Is This Project?

**Mecca Digital** is a **link-in-bio** platform (similar to Linktree) with extra **bento-style widgets**, portfolio gallery, file uploads, themes, and analytics.

### Problem it solves

Social platforms allow only **one link** in a bio. Creators need a single URL that opens a mini landing page with:

- Multiple outbound links (GitHub, shop, blog, calendar, etc.)
- A profile header (avatar, name, bio)
- Optional video, résumé, portfolio projects
- Rich widget blocks (stats, gallery, testimonials)
- Branded themes

### Public URL pattern

| Environment | Public profile URL | API base |
|-------------|-------------------|----------|
| Local (Vite) | `http://localhost:8080/{slug}` | `http://localhost:8002/api` |
| Production (planned) | `https://meccadigital.com/{slug}` | `https://api.meccadigital.com/api` |

Example: `http://localhost:8080/smoke817393`

### Target users

- Creators, developers, freelancers who want one shareable link for all their content
- Teams building a Linktree-style SaaS with richer widgets than basic link lists

---

## 2. Repository Structure

```
bento/
├── docs/
│   ├── LINK_IN_BIO_PRODUCT_SPEC.md   # Product requirements & phases
│   └── PROJECT_GUIDE.md              # This file
├── frankin-app/                      # Laravel 12 API backend
│   ├── app/
│   │   ├── Http/Controllers/Api/     # REST API controllers
│   │   └── Models/                   # Eloquent models
│   ├── database/
│   │   ├── migrations/
│   │   └── seeders/
│   ├── routes/api.php
│   ├── docker-compose.yml
│   ├── build-and-run.ps1             # Docker helper (Windows)
│   └── tests/e2e-full.ps1            # API + proxy smoke tests
└── frontend/                         # React SPA (Vite)
    ├── src/
    │   ├── pages/web/                # Marketing + public profile
    │   ├── pages/app/                # Creator dashboard
    │   ├── pages/auth/               # Login, signup, reset
    │   ├── pages/onboarding/         # Post-signup wizard
    │   ├── lib/                      # API clients & services
    │   └── components/               # UI components
    ├── vite.config.ts                # Dev server + API proxy
    └── .env                          # VITE_API_URL
```

---

## 3. Technology Stack

### Backend (`frankin-app`)

| Layer | Technology |
|-------|------------|
| Framework | Laravel 12 |
| Runtime | PHP 8.4 |
| App server | FrankenPHP + Laravel Octane (default local profile) |
| Alternative | PHP-FPM + Nginx (`php-fpm` Docker profile) |
| Database | PostgreSQL 16 |
| Cache / sessions | Redis 7 |
| Auth | Session cookies (Laravel `web` guard) |
| File storage | Local `public` disk (`storage/app/public`) |
| Admin (legacy) | Livewire + Flux UI (separate from React SPA) |

### Frontend (`frontend`)

| Layer | Technology |
|-------|------------|
| Framework | React 18 |
| Build | Vite 5 |
| Language | TypeScript |
| Routing | React Router 6 |
| Styling | Tailwind CSS 3 + shadcn/ui |
| Icons | Lucide React |
| State / data | TanStack Query, Zustand (partial) |
| Dark mode | next-themes |
| Fonts | Inter |

### Infrastructure (local Docker)

| Service | Container | Host port |
|---------|-----------|-----------|
| FrankenPHP | `frankin-app-frankenphp` | 8002 (HTTP), 8445 (HTTPS) |
| PostgreSQL | `frankin-app-postgres` | 5434 |
| Redis | `frankin-app-redis` | 6380 |
| Vite (optional in Docker) | `frankin-app-node` | 5175 |
| pgAdmin (optional) | `frankin-app-pgadmin4` | 5054 |

---

## 4. Architecture Overview

```mermaid
flowchart TB
    subgraph Browser
        SPA[React SPA :8080]
    end

    subgraph Backend
        FP[FrankenPHP / Octane :8002]
        API[Laravel API routes]
        DB[(PostgreSQL)]
        RD[(Redis)]
        FS[storage/app/public]
    end

    SPA -->|fetch /api/* credentials:include| FP
    FP --> API
    API --> DB
    API --> RD
    API --> FS

    Visitor[Visitor] -->|GET /{slug}| SPA
    SPA -->|GET /api/profiles/{slug}| FP
```

### Request flow (authenticated dashboard)

1. User logs in via `POST /api/login` → Laravel sets **session cookie**
2. React sends `credentials: "include"` on every API call
3. Dashboard pages call service modules in `frontend/src/lib/*`
4. Laravel validates session via `auth` middleware

### Request flow (public profile)

1. Visitor opens `http://localhost:8080/{slug}`
2. React loads `PublicProfile.tsx` → `GET /api/profiles/{slug}` (no auth)
3. Response includes profile, links, theme, blocks, projects, social links
4. Page view recorded via `POST /api/profiles/{slug}/view`
5. Link clicks go through `GET /api/r/{linkId}` (tracks click, then redirects)

---

## 5. How It Works (End-to-End)

### 5.1 Sign up & onboarding

```mermaid
flowchart LR
    A[Signup /signup] --> B[Register API]
    B --> C[Profile + slug created]
    C --> D[Onboarding socials]
    D --> E[Onboarding content]
    E --> F[Finish + publish]
    F --> G[Dashboard]
```

1. **Signup** (`/signup`): user claims a slug + email/password
2. **Register** (`POST /api/register`): creates `users` + `profiles` row, assigns default theme
3. **Onboarding**: social links, avatar/video upload (multipart)
4. **Complete** (`POST /api/onboarding/complete`): sets `profiles.is_published = true`
5. User lands in **dashboard** at `/dashboard`

### 5.2 Public link page assembly

When someone visits `/{slug}`, the page is built from several data sources:

| Section | Source | Dashboard to edit |
|---------|--------|-------------------|
| Header (avatar, name, bio) | `profiles` | Profile |
| Intro video | `profiles.video_url` | Files (video tab) or onboarding |
| Download résumé | `profiles.resume_url` | Files (resume tab) |
| Link buttons | `links` table | Links |
| Featured link cards | `links` with `is_featured` + `thumbnail_url` | Links |
| Portfolio grid | `projects` table | Gallery |
| Bento widgets | `blocks` on default `pages` row | Widgets |
| Social chips | `social_links` | Profile / Connect GitHub |
| Theme colors | `profiles.theme_id` → `themes` | Appearance |

### 5.3 Themes — save & apply

**Saving (dashboard):**

1. User picks a theme on `/dashboard/appearance`
2. Clicks **Save theme** → `PUT /api/profile` with `{ "theme_id": 3 }`
3. Stored as `profiles.theme_id` (foreign key to `themes` table)

**Applying (public page):**

1. `GET /api/profiles/{slug}` returns `theme: { id, name, config }`
2. Frontend calls `resolvePublicTheme(profile.theme)` in `frontend/src/lib/publicTheme.ts`
3. Theme **name** (Classic, Midnight, Sunset, Minimal, Neon) maps to hardcoded Tailwind classes
4. Classes are applied to page background, link buttons, cards, widgets, text, social chips

> **Important:** Theme CSS classes must exist in frontend **source files** so Tailwind JIT includes them. Database `config` JSON is metadata; rendering uses `PUBLIC_THEME_PRESETS` keyed by theme name.

### 5.4 Links & click tracking

- **CRUD:** `/dashboard/links` → `/api/links`
- **Reorder:** drag-and-drop → `POST /api/links/reorder` with `{ "ids": [3,1,2] }`
- **Featured thumbnails:** optional `thumbnail_url` + `is_featured` for Linktree-style cards
- **Public click:** href points to `/api/r/{id}` → records `link_clicks` → HTTP redirect to destination URL

### 5.5 Widgets (bento blocks)

Blocks belong to a profile's default **page** (`pages.slug = 'main'`).

| Block type | Content shape | Public render |
|------------|---------------|---------------|
| `hero` | `{ title, subtitle, image_url }` | Wide banner card |
| `stats` | `{ items: [{ label, value }] }` | 3-column stat row |
| `gallery` | `{ images: [{ url, caption }] }` | 2×2 image grid |
| `testimonials` | `{ items: [{ quote, author }] }` | Quote card |

Dashboard: `/dashboard/widgets` — add, drag-reorder, inline edit, delete.

### 5.6 Files (unified uploads)

`/dashboard/files` replaces separate Resume / Documents / Videos pages.

| Tab | `kind` | Max size | Notes |
|-----|--------|----------|-------|
| Images | `image` | 5 MB | jpeg, png, webp, gif |
| Videos | `video` | 100 MB | mp4, webm |
| Documents | `document` | 10 MB | pdf, doc, docx |
| Resume | `resume` | 10 MB | Replaces previous; sets `profiles.resume_url` |

API: `POST /api/media` (multipart), `GET /api/media`, `DELETE /api/media/{id}`

Files stored at `storage/app/public/media/{profileId}/{kind}/` and served via `/storage/...`.

### 5.7 Gallery (portfolio projects)

`/dashboard/portfolio` — add projects with cover image (upload or URL), title, description, optional link.

API: `/api/projects` CRUD + reorder.  
Public page shows a **Portfolio** grid below links.

### 5.8 Analytics

- **Page views:** `page_views` table, recorded on public profile load
- **Link clicks:** `link_clicks` table, recorded on `/api/r/{link}` redirect
- **Dashboard:** `/dashboard/analytics` → `GET /api/analytics/summary`

---

## 6. Database Model

### Core entities

```mermaid
erDiagram
    users ||--o| profiles : has
    profiles ||--o{ links : has
    profiles ||--o{ social_links : has
    profiles ||--o{ projects : has
    profiles ||--o{ pages : has
    profiles ||--o{ media : has
    profiles }o--|| themes : uses
    pages ||--o{ blocks : contains
    links ||--o{ link_clicks : tracks
    pages ||--o{ page_views : tracks
```

### Important tables

| Table | Purpose |
|-------|---------|
| `users` | Auth (email, password, name) |
| `profiles` | Public identity: slug, bio, avatar, video, resume, theme_id, is_published |
| `themes` | Preset themes (name + JSON config) |
| `links` | Link buttons: title, url, thumbnail_url, is_featured, position, is_active |
| `social_links` | Platform + username pairs |
| `pages` | Multi-page support; default `main` page holds blocks |
| `blocks` | Widget JSON content + type + position |
| `projects` | Portfolio items: title, description, url, cover_url, position |
| `media` | Uploaded files: url, kind, mime, size |
| `page_views` | Analytics: profile/page views |
| `link_clicks` | Analytics: per-link click counts |

### Profile slug rules

- 5–100 characters
- Lowercase letters, numbers, hyphens only: `[a-z0-9-]+`
- Checked via `GET /api/profiles/check-slug?slug=...`

---

## 7. Backend API Reference

Base URL (local): `http://localhost:8002/api`

### Public (no auth)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/profiles/check-slug` | Slug availability |
| GET | `/profiles/{slug}` | Full public profile payload |
| POST | `/profiles/{slug}/view` | Record page view |
| GET | `/themes` | List theme presets |
| GET | `/creators` | Explore page creators |
| GET | `/categories` | Creator categories |
| GET | `/r/{link}` | Track click + redirect |

### Auth

| Method | Path | Description |
|--------|------|-------------|
| POST | `/register` | Create account |
| POST | `/login` | Session login |
| POST | `/logout` | End session |
| GET | `/me` | Current user + profile |
| POST | `/forgot-password` | Request reset email |
| POST | `/password/reset` | Reset with token |

### Authenticated (`auth` middleware)

| Method | Path | Description |
|--------|------|-------------|
| GET/PUT | `/profile` | Read/update profile (incl. theme_id) |
| GET | `/analytics/summary` | Views + clicks totals |
| GET/POST/PUT/DELETE | `/links` | Link CRUD |
| POST | `/links/reorder` | Reorder links |
| GET/POST/PUT/DELETE | `/blocks` | Widget CRUD |
| POST | `/blocks/reorder` | Reorder widgets |
| GET/POST/DELETE | `/media` | File list / upload / delete |
| GET/POST/PUT/DELETE | `/projects` | Portfolio CRUD |
| POST | `/projects/reorder` | Reorder projects |
| POST | `/onboarding/social-links` | Save social usernames |
| POST | `/onboarding/profile` | Avatar/video upload |
| POST | `/onboarding/complete` | Publish profile |

All authenticated routes use **session cookies** — frontend must send `credentials: "include"`.

CSRF: API routes under `api/*` are exempt from CSRF (see `bootstrap/app.php`).

---

## 8. Frontend Routes & Pages

### Marketing & public

| Route | Page | Auth |
|-------|------|------|
| `/` | Landing (Index) | No |
| `/explore` | Creator discovery | No |
| `/login` | Login | No |
| `/signup` | Signup | No |
| `/{slug}` | Public link-in-bio page | No |

### Onboarding (auth required)

| Route | Purpose |
|-------|---------|
| `/onboarding/socials` | Add social accounts |
| `/onboarding/content` | Avatar, video, bio |
| `/onboarding/finish` | Copy link + celebrate |

### Dashboard (auth required)

| Route | Purpose | API wired? |
|-------|---------|------------|
| `/dashboard` | Overview + quick actions | Yes |
| `/dashboard/links` | Link editor | Yes |
| `/dashboard/profile` | Name, bio, socials | Yes |
| `/dashboard/appearance` | Theme picker | Yes |
| `/dashboard/widgets` | Bento blocks | Yes |
| `/dashboard/files` | Images, videos, docs, résumé | Yes |
| `/dashboard/portfolio` | Gallery projects | Yes |
| `/dashboard/analytics` | Views & clicks | Yes |
| `/dashboard/connect-github` | GitHub username | Yes |
| `/dashboard/settings` | Account settings | Partial |
| `/dashboard/skills` | Skills list | Placeholder (local only) |
| `/dashboard/work` | Work history | Placeholder |
| `/dashboard/subscriptions` | Pricing cards | Placeholder |

Legacy redirects:

- `/dashboard/upload-videos` → `/dashboard/files?tab=video`
- `/dashboard/documents` → `/dashboard/files?tab=document`
- `/dashboard/upload-resume` → `/dashboard/files?tab=resume`
- `/dashboard/connect-linkedin` → `/dashboard/profile`

---

## 9. Feature Deep Dives

### 9.1 Authentication

- **Session-based** (not JWT)
- Cookie set on login; sent automatically with `fetch(..., { credentials: "include" })`
- `ProtectedRoute` component redirects unauthenticated users to `/login`
- After logout, `GET /api/me` returns 401 (not 500)

### 9.2 Public profile rendering

Main file: `frontend/src/pages/web/PublicProfile.tsx`

Component tree:

```
PublicProfilePage
├── ProfileHeader          (avatar, name, bio, share)
├── [content card]
│   ├── Video (optional)
│   ├── Résumé button (optional)
│   ├── PublicLinkStack    (featured + standard links)
│   ├── PublicProjectGrid  (portfolio)
│   └── PublicBlockGrid    (widgets)
├── PublicSocialBar
└── Footer branding
```

Theme resolution: `frontend/src/lib/publicTheme.ts`

### 9.3 Dashboard navigation

`frontend/src/components/DashboardLayout.tsx` — pill-style header nav.

Only the **active** route expands with a label (uses React Router `NavLink` with `end` on Home so `/dashboard` does not stay active on sub-routes).

### 9.4 File upload flow

1. User selects file in Files page
2. `FormData` POST to `/api/media` with `file` + `kind`
3. Laravel stores file on `public` disk, creates `media` row
4. For `kind=resume`, also updates `profiles.resume_url`
5. Public profile shows "Download résumé" button

Run once after setup:

```powershell
docker compose exec frankenphp php artisan storage:link
```

### 9.5 Link redirect & analytics

`RedirectController` records:

- `link_id`, `ip`, user agent, referrer, timestamp

Then returns HTTP redirect to the link's stored URL.

---

## 10. Local Development Setup

### Prerequisites

- Docker Desktop (running)
- Node.js 18+ (for frontend)
- Git

### Step 1 — Backend

```powershell
cd e:\bento\frankin-app

# Copy env if needed
copy .env.example .env
# Set APP_KEY via: php artisan key:generate (inside container)

# Build & start (FrankenPHP profile — recommended)
.\build-and-run.ps1 -Profile frankenphp

# Migrations + storage + demo data
docker compose exec frankenphp php artisan migrate --force
docker compose exec frankenphp php artisan storage:link
docker compose exec frankenphp php artisan db:seed --class=ThemeSeeder --force
docker compose exec frankenphp php artisan db:seed --class=SmokeDemoSeeder --force
```

Backend API: **http://localhost:8002**

> **Note:** With `-Profile frankenphp`, there is **no `app` container**. Run artisan inside **`frankenphp`**:
> `docker compose exec frankenphp php artisan ...`

### Step 2 — Frontend

```powershell
cd e:\bento\frontend

# Ensure .env exists
# VITE_API_URL=http://localhost:8002

npm install
npm run dev
```

Frontend SPA: **http://localhost:8080**

Vite proxies `/api/*` → `http://localhost:8002` (see `vite.config.ts`).

> Restart Vite after changing `frontend/.env`.

### Step 3 — Verify

| URL | Expected |
|-----|----------|
| http://localhost:8080/ | Landing page |
| http://localhost:8080/smoke817393 | Demo public profile |
| http://localhost:8080/login | Login |
| http://localhost:8002/api/themes | JSON theme list |

Run full smoke tests:

```powershell
cd e:\bento\frankin-app
.\tests\e2e-full.ps1
```

---

## 11. Docker & Services

### Profiles (`build-and-run.ps1`)

| Profile | Services started | Use case |
|---------|------------------|----------|
| `frankenphp` | postgres, redis, frankenphp | **Default** — fast Octane API on :8002 |
| `php-fpm` | postgres, redis, app, web | Traditional Nginx + PHP-FPM on :8001 |
| `all` | Everything | Full stack |

Optional flags: `-WithNode`, `-WithPgAdmin`, `-Down`, `-Logs`, `-BuildOnly`, `-NoBuild`

### Common commands

```powershell
cd e:\bento\frankin-app

docker compose ps
docker compose logs -f frankenphp
docker compose exec frankenphp php artisan migrate --force
docker compose exec frankenphp php artisan db:seed --class=SmokeDemoSeeder --force
docker compose down
```

---

## 12. Environment Variables

### Backend (`frankin-app/.env`)

| Variable | Purpose |
|----------|---------|
| `APP_URL` | Base URL for generated links |
| `APP_KEY` | Laravel encryption key |
| `DB_*` | PostgreSQL connection (host `postgres` in Docker) |
| `REDIS_HOST` | Redis (`redis` in Docker) |
| `CACHE_STORE` | `redis` |
| `SESSION_DRIVER` | `database` |
| `FILESYSTEM_DISK` | `local` (use `public` disk in code for uploads) |

### Frontend (`frontend/.env`)

| Variable | Purpose |
|----------|---------|
| `VITE_API_URL` | API origin — **must be HTTP port 8002**, not HTTPS 8445 |

Example:

```env
VITE_API_URL=http://localhost:8002
```

If API URL is wrong or stale, public profiles may 404 or hit the wrong host. Always restart Vite after changes.

---

## 13. Testing

### E2E smoke script

`frankin-app/tests/e2e-full.ps1` — tests:

- Public profile, themes, creators, slug check
- Register/login/logout flow
- Links, blocks, media, projects CRUD
- Analytics summary
- Frontend proxy through `:8080`

Requires backend on `:8002` and optionally frontend on `:8080`.

### PHPUnit / Pest

Located in `frankin-app/tests/` — auth and feature tests for Laravel.

---

## 14. Demo Account & Seed Data

### Smoke demo user

| Field | Value |
|-------|-------|
| Email | `smoke817393@test.com` |
| Password | `password123` |
| Slug | `smoke817393` |
| Public URL | http://localhost:8080/smoke817393 |

### Re-seed demo content

```powershell
docker compose exec frankenphp php artisan db:seed --class=SmokeDemoSeeder --force
```

Seeds: profile, social links, links (with featured thumbnails), blocks, portfolio projects, résumé URL, Sunset/Neon theme assignment.

### Theme presets

Seeded by `ThemeSeeder`: **Classic**, **Midnight**, **Sunset**, **Minimal**, **Neon**

---

## 15. Key Source Files

### Backend

| File | Purpose |
|------|---------|
| `routes/api.php` | All REST routes |
| `app/Http/Controllers/Api/ProfileController.php` | Public + owner profile |
| `app/Http/Controllers/Api/LinkController.php` | Links CRUD |
| `app/Http/Controllers/Api/BlockController.php` | Widgets CRUD |
| `app/Http/Controllers/Api/MediaController.php` | File uploads |
| `app/Http/Controllers/Api/ProjectController.php` | Portfolio CRUD |
| `app/Http/Controllers/Api/ThemeController.php` | Theme list |
| `app/Http/Controllers/Api/RedirectController.php` | Click tracking redirect |
| `app/Http/Controllers/Api/AnalyticsController.php` | Summary stats |
| `database/seeders/SmokeDemoSeeder.php` | Demo profile data |
| `database/seeders/ThemeSeeder.php` | Theme presets |

### Frontend

| File | Purpose |
|------|---------|
| `src/App.tsx` | All routes |
| `src/lib/apiBase.ts` | API URL helper |
| `src/lib/authService.ts` | Login/logout/me |
| `src/lib/profileService.ts` | Profile + links client |
| `src/lib/blockService.ts` | Widgets client |
| `src/lib/mediaService.ts` | Files upload client |
| `src/lib/projectService.ts` | Gallery client |
| `src/lib/themeService.ts` | Theme save client |
| `src/lib/publicTheme.ts` | Public page theme presets |
| `src/pages/web/PublicProfile.tsx` | Public link page |
| `src/components/DashboardLayout.tsx` | Dashboard shell + nav |
| `vite.config.ts` | Dev server + API proxy |

---

## 16. Implementation Status

### Done (Phases 1–3)

- [x] Session auth (register, login, logout, me)
- [x] Slug-based public profiles
- [x] Links CRUD, reorder, featured thumbnails
- [x] Theme picker + public theme rendering
- [x] Onboarding flow
- [x] Analytics (views, clicks, summary)
- [x] Explore / creators API
- [x] Bento widgets (hero, stats, gallery, testimonials)
- [x] Unified Files upload (images, video, docs, résumé)
- [x] Portfolio gallery projects
- [x] OG meta tags on public profiles
- [x] Docker FrankenPHP stack + e2e tests

### Not yet implemented (Phase 4+)

- [ ] Stripe billing / subscriptions (UI placeholder exists)
- [ ] Custom domains
- [ ] Email verification fully wired in UI
- [ ] Google OAuth
- [ ] Feature gating by plan
- [ ] S3 cloud storage (local disk only today)
- [ ] Skills / Work dashboard pages (placeholders)

See `docs/LINK_IN_BIO_PRODUCT_SPEC.md` for full product roadmap.

---

## 17. Troubleshooting

### `service "app" is not running`

You started with `-Profile frankenphp`. Use:

```powershell
docker compose exec frankenphp php artisan migrate
```

Not `docker compose exec app ...`.

### Public profile 404

1. Check `frontend/.env` has `VITE_API_URL=http://localhost:8002`
2. Restart Vite (`npm run dev`)
3. Confirm profile exists and `is_published = true`
4. Confirm slug matches URL exactly

### Theme saves but page looks the same

1. Hard refresh public page (`Ctrl+Shift+R`)
2. Restart Vite after frontend theme code changes
3. Themes render via `publicTheme.ts` keyed by theme **name** — ensure theme name matches a preset (Classic, Midnight, etc.)

### Upload fails

1. Run `php artisan storage:link` inside `frankenphp` container
2. Check `storage/app/public` is writable
3. Verify file size/type limits in `MediaController`

### CORS / cookie issues

Use Vite dev server (`:8080`) with proxy — do not open frontend from a different origin than API without CORS config. Session cookies require same-site or proxied `/api` path.

### Port conflicts

| Port | Service |
|------|---------|
| 8002 | FrankenPHP HTTP |
| 8080 | Vite frontend |
| 5434 | PostgreSQL |
| 6380 | Redis (host maps to 6379 in container) |

Redis host port 6380 avoids conflict with local Redis on 6379.

---

## 18. Related Documents

| Document | Contents |
|----------|----------|
| [LINK_IN_BIO_PRODUCT_SPEC.md](./LINK_IN_BIO_PRODUCT_SPEC.md) | Product research, UX flows, phased tasks, acceptance criteria |
| [frontend/README.md](../frontend/README.md) | Vite/React template readme (if present) |

---

## Quick Command Cheat Sheet

```powershell
# Start backend
cd e:\bento\frankin-app
.\build-and-run.ps1 -Profile frankenphp

# Migrate + seed
docker compose exec frankenphp php artisan migrate --force
docker compose exec frankenphp php artisan storage:link
docker compose exec frankenphp php artisan db:seed --class=SmokeDemoSeeder --force

# Start frontend
cd e:\bento\frontend
npm run dev

# Run tests
cd e:\bento\frankin-app
.\tests\e2e-full.ps1

# Demo login
# smoke817393@test.com / password123
# http://localhost:8080/smoke817393
```

---

*Mecca Digital — Everything you create. One link in bio.*

Project Gallery

Visual Showcase

Gallery image 1
Gallery image 2
Gallery image 3
Gallery image 4
Gallery image 5
Gallery image 6
Gallery image 7
Gallery image 8
Gallery image 9
Gallery image 10
Gallery image 11
Gallery image 12

Interested in Similar Work?

Let's discuss how we can create something amazing for your business with the same level of engineering excellence.

Let’s talk about your project

Fill out the quick form and we’ll reach out.

Name
Email
Phone
Booking Date
Call Duration
Service
Message