# ECStores — Setup Guide (Phases 0–6)

Complete step-by-step instructions to rebuild the development environment from scratch on a fresh Windows 11 PC.

---

## 1. Install Laragon

Laragon provides PHP, MySQL, Apache, and Composer in a single installer.

1. Download **Laragon Full** from `laragon.org`
2. Run the installer, accept all defaults
3. Launch Laragon and click **Start All**
4. Verify Apache and MySQL indicators are green

---

## 2. Add PHP and Composer to the Windows PATH

Laragon installs PHP and Composer but doesn't always add them to the PATH automatically. Open PowerShell and run:

```powershell
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\laragon\bin\php\php-8.3.30-Win32-vs16-x64;C:\laragon\bin\composer", [EnvironmentVariableTarget]::User)
```

> **Note:** The PHP version folder name may differ. Check `C:\laragon\bin\php\` for the exact folder name and update the command accordingly.

Close and reopen PowerShell, then verify:

```powershell
php -v
composer -v
```

Both should return version numbers.

---

## 3. Enable the ZIP Extension in php.ini

Without this, Composer downloads packages slowly via git clone instead of zip.

1. Open `C:\laragon\bin\php\php-8.3.30-Win32-vs16-x64\php.ini` in VS Code
2. Search for `;extension=zip`
3. Remove the semicolon so it reads: `extension=zip`
4. Save the file

Verify in PowerShell:

```powershell
php -m | findstr zip
```

Should print `zip`.

---

## 4. Create the Laravel Project

Navigate to your working directory and create the project:

```powershell
cd "d:\#WORK\EC_WebCraft\~PRIVATE_HTML"
composer create-project laravel/laravel ecstores
cd ecstores
```

Generate the application key:

```powershell
php artisan key:generate
```

---

## 5. Create the Landlord Database

Make sure Laragon is running (Start All), then create the central database:

```powershell
& "C:\laragon\bin\mysql\mysql-8.4.3-winx64\bin\mysql.exe" -u root -e "CREATE DATABASE ecstores_central;"
```

> **Note:** The MySQL version folder name may differ. Check `C:\laragon\bin\mysql\` for the exact folder name.

---

## 6. Configure the .env File

Open `d:\#WORK\EC_WebCraft\~PRIVATE_HTML\ecstores\.env` and replace the database section with:

```
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ecstores_central
DB_USERNAME=root
DB_PASSWORD=
```

Run the default Laravel migrations:

```powershell
php artisan migrate
```

You should see the default users, cache, and jobs tables created.

---

## 7. Install stancl/tenancy

```powershell
composer require stancl/tenancy
php artisan tenancy:install
php artisan migrate
```

The `tenancy:install` command creates:
- `config/tenancy.php`
- `routes/tenant.php`
- `app/Providers/TenancyServiceProvider.php`
- `database/migrations/tenant/` folder

The second `php artisan migrate` creates the `tenants` and `domains` tables in `ecstores_central`.

---

## 8. Register the TenancyServiceProvider

Open `bootstrap/providers.php` and add the provider:

```php
return [
    AppServiceProvider::class,
    App\Providers\TenancyServiceProvider::class,
];
```

---

## 9. Update the Tenancy Config

Open `config/tenancy.php` and make two changes:

**Change the tenant model** (line ~9):
```php
'tenant_model' => App\Models\Tenant::class,
```

**Change the database prefix** (line ~54):
```php
'prefix' => 'tenant_',
```

---

## 10. Create the Custom Tenant Model

Create `app/Models/Tenant.php`:

```php
<?php

declare(strict_types=1);

namespace App\Models;

use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;

class Tenant extends BaseTenant implements TenantWithDatabase
{
    use HasDatabase, HasDomains;
}
```

---

## 11. Create the Tenant Migrations

All tenant migrations live in `database/migrations/tenant/`. These run automatically on every new tenant database when a tenant is created.

The following migration files must exist (they are already in the repository):

| File | Tables Created / Modified |
|---|---|
| `2024_01_01_000001_create_users_table.php` | `users`, `password_reset_tokens`, `sessions` |
| `2024_01_01_000002_create_cache_jobs_table.php` | `cache`, `cache_locks`, `jobs`, `job_batches`, `failed_jobs` |
| `2024_01_01_000003_create_admins_table.php` | `admins` |
| `2024_01_01_000004_create_user_profiles_table.php` | `user_profiles` |
| `2024_01_01_000005_create_categories_suppliers_table.php` | `categories`, `suppliers` |
| `2024_01_01_000006_create_products_table.php` | `products` |
| `2024_01_01_000007_create_variant_tables.php` | `product_variant_groups`, `product_variant_options`, `product_variant_combinations`, `product_variant_combination_options` |
| `2024_01_01_000008_create_orders_table.php` | `orders`, `order_items`, `order_payments` |
| `2024_01_01_000009_create_store_tables.php` | `wishlists`, `coupons`, `shipping_methods`, `site_settings` |
| `2024_01_01_000010_create_media_table.php` | `media` (Spatie Media Library — installed, reserved for future use) |
| `2024_01_01_000011_add_images_to_products_table.php` | Adds `images` JSON column to `products` |
| `2024_01_01_000012_add_stripe_connect_to_site_settings.php` | Adds `stripe_charges_enabled`, `stripe_payouts_enabled` to `site_settings` |
| `2024_01_01_000013_add_branding_files_to_site_settings.php` | Adds `logo_path`, `favicon_path`, `banner_path` to `site_settings` |

If restoring from scratch with the repository files already in place, skip to step 12.

---

## 12. Verify — Create a Test Tenant

```powershell
php artisan tinker
```

Inside tinker:

```php
\App\Models\Tenant::create(['id' => 'acme']);
exit
```

Verify both databases exist:

```powershell
& "C:\laragon\bin\mysql\mysql-8.4.3-winx64\bin\mysql.exe" -u root -e "SHOW DATABASES;"
```

You should see `ecstores_central` and `tenant_acme`.

Run the tenant migrations:

```powershell
php artisan tenants:migrate
```

Verify the tenant tables:

```powershell
& "C:\laragon\bin\mysql\mysql-8.4.3-winx64\bin\mysql.exe" -u root -e "USE tenant_acme; SHOW TABLES;"
```

You should see 25 tables.

---

## 13. Verify the App Runs

```powershell
php artisan serve
```

Visit `http://localhost:8000` — you should see the Laravel welcome page.

---

## 14. Install Filament

```powershell
composer require filament/filament
php artisan filament:install --panels
```

When prompted for a panel ID, enter `admin`.

---

## 15. Create the Admin Model and Auth Guard

Create `app/Models/Admin.php` — the model Filament uses to authenticate store owners against the tenant `admins` table. See the file in the repository.

Add the `admins` guard to `config/auth.php`:

```php
'guards' => [
    'web' => ['driver' => 'session', 'provider' => 'users'],
    'admins' => ['driver' => 'session', 'provider' => 'admins'],
],

'providers' => [
    'users' => ['driver' => 'eloquent', 'model' => App\Models\Admin::class],
    'admins' => ['driver' => 'eloquent', 'model' => App\Models\Admin::class],
],
```

---

## 16. Configure the Filament Panels

**`app/Providers/Filament/AdminPanelProvider.php`** — tenant store-owner panel, uses the `admins` guard. See the file in the repository.

**`app/Providers/Filament/SuperAdminPanelProvider.php`** — Denis's platform panel (shell only, wired up in Phase 5). See the file in the repository.

Register both in `bootstrap/providers.php`:

```php
return [
    App\Providers\AppServiceProvider::class,
    App\Providers\Filament\AdminPanelProvider::class,
    App\Providers\Filament\SuperAdminPanelProvider::class,
    App\Providers\TenancyServiceProvider::class,
];
```

---

## 17. Fix the Tenant Routes File

The `tenancy:install` command creates `routes/tenant.php` with a demo `/` route that conflicts with `routes/web.php`. Replace its contents with:

```php
<?php
declare(strict_types=1);

use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;

use App\Http\Controllers\Storefront\ProductController;

Route::middleware([
    'web',
    InitializeTenancyBySubdomain::class,
    PreventAccessFromCentralDomains::class,
])->group(function () {
    Route::get('/', [ProductController::class, 'index'])->name('storefront.index');
    Route::get('/products/{slug}', [ProductController::class, 'show'])->name('storefront.product');
});
```

---

## 18. Add Global Tenancy Middleware

Create `app/Http/Middleware/InitializeTenancyBySubdomainIfNotCentral.php` — skips tenancy init for central domains, initializes it for all tenant subdomains. This covers Livewire AJAX and Filament routes which don't use the standard web middleware group. See the file in the repository.

Register it globally in `bootstrap/app.php`:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->append(
        \App\Http\Middleware\InitializeTenancyBySubdomainIfNotCentral::class,
    );
})
```

---

## 19. Fix the .env File

Update these values in `.env`:

```
APP_NAME=ECStores
APP_URL=http://ecstores.test
SESSION_DRIVER=file
SESSION_DOMAIN=.ecstores.test
```

> **Why file sessions?** The `database` session driver breaks with multi-tenancy because Livewire AJAX requests need to read the session but run in a different DB context. File sessions live on the filesystem and are unaffected by DB switching.

---

## 20. Disable the Filesystem Tenancy Bootstrapper

In `config/tenancy.php`, comment out `FilesystemTenancyBootstrapper`:

```php
'bootstrappers' => [
    Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper::class,
    Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper::class,
    // Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper::class,
    Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,
],
```

> **Why?** This bootstrapper redirects all asset URLs through `/tenancy/assets/` when running in tenant context, which breaks Filament's static CSS and JS files served from `public/`. Re-enable when adding Spatie Media Library.

---

## 21. Set Up the Laragon Virtual Host

The project path contains `#` which Apache treats as a comment character. Create a Windows directory junction to give Apache a clean path:

```powershell
New-Item -ItemType Junction -Path "C:\laragon\www\ecstores" -Target "D:\#WORK\EC_WebCraft\~PRIVATE_HTML\ecstores"
```

Create `C:\laragon\etc\apache2\sites-enabled\ecstores.test.conf`:

```apache
<VirtualHost *:80>
    ServerName ecstores.test
    ServerAlias *.ecstores.test
    DocumentRoot "C:/laragon/www/ecstores/public"
    <Directory "C:/laragon/www/ecstores/public">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
```

Add to `C:\Windows\System32\drivers\etc\hosts` (open Notepad as Administrator):

```
127.0.0.1 ecstores.test
127.0.0.1 acme.ecstores.test
```

Restart Laragon (Stop All → Start All).

---

## 22. Attach a Domain to the Test Tenant and Create an Admin User

```powershell
php artisan tinker
```

Inside tinker:

```php
$tenant = App\Models\Tenant::find('acme');
$tenant->domains()->create(['domain' => 'acme']);
tenancy()->initialize($tenant);
App\Models\Admin::create(['name' => 'Your Name', 'email' => 'your@email.com', 'password' => 'your-password']);
exit
```

---

## 23. Verify — Log Into the Admin Panel

Visit `http://acme.ecstores.test/admin` — you should see the Filament login page with ECStores branding. Log in with the credentials created above. You should land on the dashboard showing your name in the Welcome widget.

---

## 24. Create the Storage Symlink

Product image uploads are stored in `storage/app/public/` and served via `public/storage/`. Create the symlink once:

```powershell
php artisan storage:link
```

You should see `public/storage` appear as a shortcut pointing into the storage directory. This is required for uploaded product images to be visible in the storefront and admin panel.

---

## 25. Verify — Phase 1 (Admin Panel + Storefront)

**Admin panel check:**

1. Visit `http://acme.ecstores.test/admin` and log in
2. You should see three resources in the left nav under **Catalog**: Products, Categories, Suppliers
3. You should see **Site Settings** in the nav (Settings group)
4. Create a test product: add a name (slug auto-fills), set a price, upload an image, add a variant group with options — save successfully
5. Edit the product — the uploaded image should display in the FileUpload field (not stuck loading)

**Storefront check:**

1. Visit `http://acme.ecstores.test` — you should see the product catalogue
2. The test product should appear with its image (make sure `is_discontinued` is unchecked)
3. Click the product — the detail page should show the image, price, and description
4. Visit `http://acme.ecstores.test/admin/site-settings` and save a store name — the header on the storefront should update

---

## 26. Phase 2 — No New Migrations

All tables needed for Phase 2 (`orders`, `order_items`, `shipping_methods`) already exist from the Phase 0 migrations. No `tenants:migrate` is required after pulling Phase 2 code.

---

## 27. Seed a Shipping Method

Checkout step 2 requires at least one active shipping method. Add one via the admin panel before testing checkout:

1. Visit `http://acme.ecstores.test/admin/shipping-methods`
2. Click **New Shipping Method**
3. Enter name (e.g. `Standard Shipping`), price (e.g. `10.00`), leave Active on
4. Save

---

## 28. Verify — Phase 2 (Cart, Checkout, Orders)

**Admin — Variant Combinations:**
1. Edit a product that has variant groups defined
2. Scroll to the **Variant Combinations** tab at the bottom of the page
3. Click **Generate Combinations** — rows should appear (one per combination of options)
4. Click **Edit** on a row and set a SKU or price override

**Admin — Orders & Shipping:**
1. Visit `http://acme.ecstores.test/admin/shipping-methods` — Shipping Methods resource loads
2. Visit `http://acme.ecstores.test/admin/orders` — Orders list loads (empty until a test order is placed)

**Storefront — Full Checkout Flow:**
1. Visit a product page — variant option buttons appear, clicking them is reactive
2. Click **Add to Cart** — success message appears; cart count badge in header increments
3. Visit `/cart` — item shows with qty controls, remove button, subtotal, and "Proceed to Checkout"
4. Click **Proceed to Checkout** — 3-step wizard loads
5. Fill in contact/billing info → Continue to Shipping
6. Select a shipping method → Review Order
7. Review summary → click **Place Order**
8. Order confirmation page shows with order number, items, and totals
9. Visit `http://acme.ecstores.test/admin/orders` — the order appears with status **Pending** and an **Order Items** tab

---

## 29. Phase 3 — No New Migrations

All tables needed for Phase 3 (`user_profiles`, `wishlists`) already exist from the Phase 0 migrations. No `tenants:migrate` is required after pulling Phase 3 code.

---

## 30. Verify — Phase 3 (Customer Accounts, Wishlist)

**Storefront auth:**
1. Visit `http://acme.ecstores.test/register`
2. Create an account — you should be logged in and see **My Account** / **Log out** in the header
3. Click **Log out** — header returns to **Sign in** / **Register**
4. Visit `/account` while logged out — should redirect to `/login`
5. Log in at `/login` — should redirect back to `/account`

**Profile & address:**
6. On the **Profile** tab, fill in your address fields and click **Save Changes**
7. Add a product to the cart and visit checkout — your name, email, and address should be pre-filled in Step 1

**Order history:**
8. Complete a checkout while logged in
9. Visit `/account/orders` — the order should appear with status, date, and items listed

**Wishlist:**
10. Visit the catalogue page — each product card should have a heart button in the top-right corner
11. Click the heart while logged in — it should fill red immediately (Livewire reactive)
12. Click again — should empty back to grey
13. Visit a product detail page — heart button + "Save to wishlist" text should appear below the variant selector
14. Visit `/account/wishlist` — wishlisted products appear as cards with "View Product" links
15. Click the heart on a wishlist card — item removed from the page
16. Log out and click a heart on the catalogue page — should redirect to `/login`

---

## 31. Run the Phase 5 Landlord Migrations

Phase 5 adds three tables to the **landlord** database (`ecstores_central`). These are NOT tenant migrations.

```powershell
php artisan migrate
```

You should see three new migrations run:
- `create_super_admins_table`
- `create_plans_table`
- `add_suspended_at_to_tenants_table`

---

## 32. Add the Hosts Entry for the Central Domain

The super-admin panel lives at `http://ecstores.test/super` (the central domain, not a tenant subdomain). Verify `ecstores.test` is already in your hosts file — it should be from Step 21. If not, add it:

```
127.0.0.1 ecstores.test
```

---

## 33. Create Your Super-Admin Account

```powershell
php artisan tinker
```

Inside tinker:

```php
App\Models\SuperAdmin::create([
    'name'     => 'Denis Gallant',
    'email'    => 'denis.gallant@gmail.com',
    'password' => 'your-password-here',
]);
exit
```

---

## 34. Verify — Log Into the Super-Admin Panel

Visit `http://ecstores.test/super` — you should see the Filament login page branded "ECStores — Platform" with a slate/grey colour scheme (different from the tenant amber admin).

Log in with the credentials created in Step 33. You should land on a dashboard showing:
- Platform Overview widget: Total Tenants, Active Stores, Suspended counts
- **Tenants** resource in the left nav (Platform group)
- **Plans** resource in the left nav (Platform group)

---

## 35. Provision a New Tenant via the Super-Admin Panel

This is the full provisioning workflow — use it instead of `tinker` for all new tenants going forward.

1. Visit `http://ecstores.test/super/tenants`
2. Click **Provision New Tenant**
3. Fill in:
   - Store Name: `Acme Store`
   - Subdomain: `acme` (must match what's in your hosts file)
   - Owner Name: `Your Name`
   - Owner Email: `your@email.com`
   - Temporary Password: `your-password`
4. Click **Provision New Tenant** in the modal

What happens behind the scenes:
1. `Tenant::create(['id' => 'acme', 'name' => '...', 'email' => '...'])`
2. Domain record created: `$tenant->domains()->create(['domain' => 'acme'])`
3. `php artisan tenants:migrate --tenants=acme` runs all tenant migrations on the new DB
4. Tenant context initialized → `SiteSettings` seeded with store name, `ShippingMethod` seeded with Standard Shipping $10, `Admin` user created
5. Tenant context ended

**Expected result:** Success notification. The tenant appears in the list as Active.

> **Note:** If you already have a manually-created `acme` tenant from earlier steps, use a different subdomain (e.g. `test2`) for this test — the subdomain must be unique.

---

## 36. Verify — Tenant Suspend & Reactivate

1. In the Tenants list, find a tenant and click **Suspend** (three-dot menu or row action)
2. Confirm the suspension
3. Visit that tenant's storefront (e.g. `http://acme.ecstores.test`)

**Expected result:** A "Store Temporarily Unavailable" page with 503 status — the storefront is blocked but the admin panel still works.

4. Go back to the super-admin Tenants list and click **Reactivate**
5. Visit the storefront again

**Expected result:** Storefront loads normally.

---

## Quick Reference — Common Commands

| Task | Command |
|---|---|
| Start the dev server (alternative to Laragon) | `php artisan serve` |
| Run landlord migrations | `php artisan migrate` |
| Run tenant migrations on all tenants | `php artisan tenants:migrate` |
| Create the storage symlink (once per machine) | `php artisan storage:link` |
| Open the Laravel REPL | `php artisan tinker` |
| Create a tenant | `\App\Models\Tenant::create(['id' => 'slug'])` |
| Attach a domain to a tenant | `$tenant->domains()->create(['domain' => 'slug'])` |
| List all routes | `php artisan route:list` |
| Clear all caches | `php artisan optimize:clear` |

---

## 37. Phase 4 — Stripe Setup

### Install the Stripe CLI

Download from `stripe.com/docs/stripe-cli`. After installing, log in:

```powershell
stripe login
```

Follow the browser prompt to authenticate.

### Add Stripe keys to .env

Get your keys from Stripe Dashboard → Developers → API Keys:

```
STRIPE_KEY=pk_test_your_publishable_key
STRIPE_SECRET=sk_test_your_secret_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
```

To get the webhook secret, start the Stripe CLI listener:

```powershell
stripe listen --forward-to ecstores.test/stripe/webhook
```

Copy the `whsec_...` value it prints and paste into `.env`.

### Run the Cashier landlord migrations

```powershell
php artisan migrate
```

This creates: `customer_columns` on `tenants`, `subscriptions`, `subscription_items` tables in `ecstores_central`.

### Run the Stripe Connect tenant migration

```powershell
php artisan tenants:migrate
```

This runs migration `000012_add_stripe_connect_to_site_settings` on every existing tenant DB.

### Enable Stripe Connect on your Stripe account

1. Visit `dashboard.stripe.com/connect`
2. Click **Get started with Connect**
3. Select **Platform or marketplace** as the business model
4. Complete the form — this is your platform's registration, not a merchant's

### Create your subscription plans in Stripe

1. Stripe Dashboard → Products → **Add product**
2. Name it (e.g. "ECStores Starter"), add a recurring price (e.g. $29/month)
3. Copy the `price_xxxxx` ID
4. In the super-admin panel → Plans → edit the plan → paste the Stripe Price ID

### Verify Phase 4

1. Log into a tenant's admin panel → **Settings → Stripe Connect**
2. Click **Connect Stripe Account** — goes through Stripe Express onboarding
3. After completing, the page should show "Charges Enabled"
4. Add a product to the cart on the storefront and complete checkout
5. On Step 4, enter test card `4242 4242 4242 4242`, any future expiry, any CVC
6. Click **Pay & Place Order** — should redirect to the order confirmation page showing "Payment Received"
7. Check Stripe Dashboard → Payments — the test payment should appear as Succeeded

---

## 38. Phase 6 — Branding Setup

### Run the branding tenant migration

If you haven't already (it's included in `tenants:migrate`):

```powershell
php artisan tenants:migrate
```

This runs migration `000013_add_branding_files_to_site_settings` on every tenant DB.

### Verify Phase 6

1. Log into a tenant's admin panel → **Settings → Branding Studio**
2. Change the Primary colour to red (`#DC2626`) and click **Save Branding**
3. Visit the tenant's storefront and hard-refresh (`Ctrl+Shift+R`)
4. Buttons and the cart badge should now be red
5. Upload a logo image — the store name text in the header should be replaced by the logo
6. Upload a banner image — it should appear as a full-width strip at the top of the home page

---

## Important Notes

- **Laragon must be running** (Start All) before running any `php artisan` commands that touch the database
- **MySQL folder path** — if you reinstall Laragon and the MySQL version changes, update the mysql.exe path in any commands above
- **PHP folder path** — same applies to the PATH entry set in Step 2
- This project uses **two separate databases**: `ecstores_central` (landlord) and one `tenant_{slug}` per merchant
- Tenant databases are created **automatically** when you create a Tenant model record — you never create them manually
- **Each tenant needs a domain record** — after creating a tenant, run `$tenant->domains()->create(['domain' => 'slug'])` or the subdomain identification middleware will throw a 500 error
- **Do not rename the `#WORK` parent folder** — the Claude Code conversation history is tied to the project path; use the Laragon junction instead
- **Windows storage permissions** — on a fresh clone, Apache/PHP may get "Access is denied" when compiling Blade views. Fix once per machine:
  ```powershell
  & icacls "d:\#WORK\EC_WebCraft\~PRIVATE_HTML\ecstores\storage" /grant "Everyone:(OI)(CI)F" /T
  php artisan view:clear
  ```
  If the error keeps occurring, add `storage\framework\views` as a Windows Defender exclusion folder.
