# WhatsApp Cloud API — Admin Panel

> **Last updated:** 2026-03-27
> A Laravel 13 SaaS admin panel for managing companies, pricing plans, apps, and WhatsApp Cloud API integrations.

---

## What This Project Is

This is a **multi-tenant SaaS admin dashboard** built on Laravel 13. It lets you:

- Register and manage **companies** (businesses/stores using WhatsApp API)
- Create and sell **pricing plans** with feature limits and app bundles
- Manage **apps / plugins** (free and premium) available to subscribers
- Configure **system settings** that sync directly to `.env`
- View **application logs** in a human-readable format

Each company has one owner user. Agents (future feature) will share the same company without creating a new one.

---

## Tech Stack

| Layer      | Technology                        |
|------------|-----------------------------------|
| Framework  | Laravel 13 (PHP 8.3+)             |
| Database   | MySQL (remote: 157.180.4.221)     |
| Frontend   | Blade templates, vanilla CSS/JS   |
| Charts     | Chart.js 4.4.0 (CDN)              |
| Icons      | FontAwesome 6.5.0 (CDN)           |
| Auth       | Laravel built-in session auth     |
| Storage    | Local or Amazon S3 / DO Spaces    |
| Queue      | Database driver                   |
| Sessions   | Database driver                   |
| Broadcast  | Pusher (configurable)             |

---

## Quick Start

### 1 — Requirements
- PHP 8.3+
- Composer
- MySQL database
- XAMPP or any web server (or `php artisan serve`)

### 2 — Install
```bash
cd /Applications/XAMPP/xamppfiles/htdocs/WhatsApp/whatsappcloudapi
composer install
cp .env.example .env        # or edit .env directly
php artisan key:generate
php artisan migrate
php artisan storage:link    # required for local file storage
php artisan serve           # runs at http://localhost:8000
```

### 3 — Default Login
```
Email:    admin@admin.com
Password: admin123
```

> If the user doesn't exist, register at `/register` — first registered user becomes admin.

---

## Project Structure

```
whatsappcloudapi/
│
├── app/
│   ├── Http/Controllers/
│   │   ├── Auth/
│   │   │   ├── LoginController.php        login + logout
│   │   │   └── RegisterController.php     creates User + Company atomically (DB transaction)
│   │   ├── DashboardController.php        dashboard stats + charts
│   │   ├── CompaniesController.php        company list, add, delete (search + paginate)
│   │   ├── PricingPlansController.php     plans CRUD + toggle active
│   │   ├── AppsController.php             apps catalog CRUD + toggle active
│   │   └── SettingsController.php         settings save → DB + .env sync
│   │
│   └── Models/
│       ├── User.php                       fillable, hidden, casts — 25+ columns
│       ├── Company.php                    SoftDeletes, belongsTo(User)
│       ├── PricingPlan.php                features_array accessor, decimal cast
│       ├── App.php                        app catalog — free + premium
│       └── Setting.php                    key/value store — get() + set() helpers
│
├── database/migrations/
│   ├── 0001_01_01_000000_create_users_table.php
│   ├── 0001_01_01_000001_create_cache_table.php
│   ├── 0001_01_01_000002_create_jobs_table.php
│   ├── 2026_03_26_172321_create_companies_table.php
│   ├── 2026_03_26_212107_create_pricing_plans_table.php
│   ├── 2026_03_26_213433_create_apps_table.php
│   ├── 2026_03_26_213434_alter_pricing_plans_period.php   ENUM fix
│   └── 2026_03_26_215843_create_settings_table.php        key/value + defaults seeded
│
├── resources/views/
│   ├── auth/
│   │   ├── login.blade.php
│   │   └── register.blade.php
│   ├── dashboard.blade.php
│   ├── companies.blade.php
│   ├── pricing-plans.blade.php
│   ├── apps.blade.php
│   └── settings.blade.php
│
├── routes/web.php                         all routes
├── .env                                   DB + AWS + Pusher credentials (auto-updated by Settings)
├── CLAUDE.md                              technical reference for Claude AI
└── README.md                              this file
```

---

## All Routes

| Method   | URI                               | Name                    | What it does                        |
|----------|-----------------------------------|-------------------------|-------------------------------------|
| GET      | `/`                               | —                       | Redirect → login                    |
| GET      | `/login`                          | `login`                 | Show login form                     |
| POST     | `/login`                          | —                       | Authenticate user                   |
| POST     | `/logout`                         | `logout`                | Logout + redirect                   |
| GET      | `/register`                       | `register`              | Show register form                  |
| POST     | `/register`                       | —                       | Create User + Company               |
| GET      | `/dashboard`                      | `dashboard`             | Dashboard with charts               |
| GET      | `/companies`                      | `companies`             | Companies list + search             |
| POST     | `/companies`                      | `companies.store`       | Add new company + owner user        |
| DELETE   | `/companies/{company}`            | `companies.destroy`     | Soft-delete company                 |
| GET      | `/pricing-plans`                  | `pricing-plans`         | Plans grid + table                  |
| POST     | `/pricing-plans`                  | `pricing-plans.store`   | Create plan                         |
| DELETE   | `/pricing-plans/{plan}`           | `pricing-plans.destroy` | Delete plan                         |
| PATCH    | `/pricing-plans/{plan}/toggle`    | `pricing-plans.toggle`  | Toggle active/inactive              |
| GET      | `/apps`                           | `apps`                  | Apps catalog (free + premium)       |
| POST     | `/apps`                           | `apps.store`            | Create app                          |
| DELETE   | `/apps/{app}`                     | `apps.destroy`          | Delete app                          |
| PATCH    | `/apps/{app}/toggle`              | `apps.toggle`           | Toggle app active                   |
| GET      | `/settings`                       | `settings`              | Settings page (5 tabs)              |
| POST     | `/settings`                       | `settings.update`       | Save settings → DB + .env           |

> All routes except login/logout/register are protected by `auth` middleware.

---

## Database Tables

### `users`
Main user accounts. Each user can own one company.

| Column              | Type              | Notes                          |
|---------------------|-------------------|--------------------------------|
| id                  | bigIncrements     |                                |
| name                | string            |                                |
| email               | string unique     |                                |
| password            | string hashed     |                                |
| phone               | string nullable   |                                |
| whatsapp_num        | string nullable   |                                |
| business_name       | string nullable   |                                |
| company_id          | FK → companies    | set after company is created   |
| plan_id             | FK → plans        |                                |
| plan_status         | string nullable   | active / trial / expired       |
| trial_ends_at       | timestamp null    |                                |
| access_token        | text nullable     | WhatsApp API token             |
| agents              | text nullable     | JSON agent config              |
| expotoken           | string nullable   | push notifications             |
| two_factor_*        | various           | 2FA fields                     |
| created_at/updated_at | timestamps      |                                |

### `companies`
One per business. Soft-deleted (has `deleted_at`).

| Column      | Type              | Notes                    |
|-------------|-------------------|--------------------------|
| id          | bigIncrements     |                          |
| name        | string            |                          |
| logo        | string nullable   |                          |
| cover       | string nullable   |                          |
| lat / lng   | decimal(10,7)     | map coordinates          |
| address     | text nullable     |                          |
| currency    | string(10)        |                          |
| description | text nullable     |                          |
| user_id     | FK → users        | owner                    |
| deleted_at  | timestamp null    | soft delete              |
| timestamps  | created/updated   |                          |

### `pricing_plans`

| Column            | Type                | Notes                              |
|-------------------|---------------------|------------------------------------|
| id                | bigIncrements       |                                    |
| name              | string              |                                    |
| description       | text null           |                                    |
| features          | text null           | comma-separated list               |
| price             | decimal(10,2)       |                                    |
| period            | enum                | 1month / 3months / 6months / 1year |
| payment_processor | string null         | stripe / paypal / razorpay / manual|
| campaigns_limit   | integer default 0   | 0 = unlimited                      |
| messages_limit    | integer default 0   | 0 = unlimited                      |
| contacts_limit    | integer default 0   | 0 = unlimited                      |
| plugins           | text null           | comma-separated app_keys           |
| is_active         | boolean default 1   |                                    |

### `apps`

| Column       | Type            | Notes                           |
|--------------|-----------------|---------------------------------|
| id           | bigIncrements   |                                 |
| app_key      | string unique   | lowercase a-z 0-9 underscore    |
| display_name | string          |                                 |
| description  | text null       |                                 |
| icon         | string          | FontAwesome class e.g. fas fa-* |
| color        | string          | hex color e.g. #25d366          |
| category     | string null     |                                 |
| page_link    | string null     | URL to app page                 |
| is_paid      | boolean         |                                 |
| price        | decimal(10,2)   |                                 |
| currency     | string(10)      | default USD                     |
| is_active    | boolean         |                                 |

### `settings`
Key/value store. Primary key is the `key` column (string).

| Setting Key          | Default Value              | Syncs to .env Key         |
|----------------------|----------------------------|---------------------------|
| project_name         | On Cloud API               | `APP_NAME`                |
| site_url             | https://apps.oncloudapi.com| `APP_URL`                 |
| subdomains           | www,apps                   | —                         |
| app_debugging        | 0                          | `APP_DEBUG`               |
| disable_landing_page | 0                          | —                         |
| wildcard_domain      | 0                          | —                         |
| pagination_count     | 100                        | —                         |
| file_storage         | local                      | `FILESYSTEM_DISK`         |
| aws_access_key       | —                          | `AWS_ACCESS_KEY_ID`       |
| aws_secret_key       | —                          | `AWS_SECRET_ACCESS_KEY`   |
| aws_region           | —                          | `AWS_DEFAULT_REGION`      |
| aws_bucket           | —                          | `AWS_BUCKET`              |
| aws_bucket_url       | —                          | `AWS_URL`                 |
| default_language     | EN                         | —                         |
| available_languages  | EN,English,FR,French       | —                         |
| timezone             | Asia/Karachi               | `APP_TIMEZONE`            |
| default_currency     | USD                        | —                         |
| money_conversion     | 1                          | —                         |
| time_format          | AM/PM                      | —                         |
| date_format          | d M Y h:i A                | —                         |
| working_time_format  | E HH:mm                    | —                         |
| phone_country_code   | US                         | —                         |
| pusher_app_id        | 1822918                    | `PUSHER_APP_ID`           |
| pusher_app_key       | 178065a...                 | `PUSHER_APP_KEY`          |
| pusher_app_secret    | 5a012b...                  | `PUSHER_APP_SECRET`       |
| pusher_app_cluster   | mt1                        | `PUSHER_APP_CLUSTER`      |
| broadcast_driver     | pusher                     | `BROADCAST_CONNECTION`    |
| app_logo             | —                          | — (filename in uploads/)  |
| msg_sending_mode     | database                   | — (cronjob / database)    |
| async_sending_mode   | database                   | — (instant / database)    |
| campaign_batch_size  | 50                         | — (1–150)                 |

---

## Pages & Features

### Dashboard (`/dashboard`)
- 12 stat cards in 3 rows: Overview, Today's Activity, Messages & Queue
- 5 Chart.js charts: Messages (7-day line), Subscriptions (donut), Campaigns (bar), Cronjob (bar), Renewals (line)
- Currently shows static/placeholder data — connect to real DB queries when tables are ready

### Companies (`/companies`)
- Search by name/email, filter by status, paginated
- Table: Company, Owner, Email, Phone, Status, Plan, Created, Actions
- Slide-out drawer (780px wide) with 6 tabs: Overview, Subscriptions, Active Apps, Payment History, Campaigns, Messages
- **Add Company** popup modal: creates User first, then Company (critical FK order)
- Delete with confirmation modal

### Pricing Plans (`/pricing-plans`)
- Cards grid + summary table view
- Each card shows: name, status toggle, price/period, limits (∞ if 0), features list, included apps pulled live from `apps` table
- **Add Plan** modal: name, description, features (comma-separated), price, period toggle (4 options), payment processor, 3 limit fields, plugin chips loaded from DB
- Period stored as: `1month` / `3months` / `6months` / `1year`

### Apps (`/apps`)
- Two sections: **Free Apps** (green header) and **Premium Apps** (gold header)
- Cards: FontAwesome icon in colored circle, name, Free/Paid badge, category, description, status, action buttons
- **Add App** modal: App Key (validates `^[a-z0-9_]+$`), Display Name, Icon with live preview, Color picker with hex sync, Category, Page Link, Paid toggle → shows Price + Currency

### Settings (`/settings`)
Seven tabs. Saving any tab updates **both** the `settings` table and the `.env` file (where applicable), then clears config cache.

| Tab              | Contains                                                          |
|------------------|-------------------------------------------------------------------|
| System           | App Logo upload, Project name, App URL, Subdomains, Pagination, 3 feature toggles |
| Storage          | Local / S3 / Spaces selector, S3 credentials, Bucket URL, composer command |
| Language & Time  | Language, Timezone, Currency, Time/Date formats, Phone code       |
| Notifications    | Pusher ID, Key, Secret, Cluster, Broadcast driver                 |
| ✉️ Sending       | Background sending mode, Async sending mode, Campaign batch size  |
| App Logs         | Flat color-coded card list, filter by level, search              |
| Activity Logs    | Coming soon                                                       |

### ✉️ Sending Settings — Must Read Before Every Send

**Every feature that sends WhatsApp messages must check these 3 settings:**

| Setting key           | Values                 | Default | Purpose |
|-----------------------|------------------------|---------|---------|
| `msg_sending_mode`    | `cronjob` / `database` | `database` | Background sending mode |
| `async_sending_mode`  | `instant` / `database` | `database` | Per-message dispatch method |
| `campaign_batch_size` | 1 – 150                | `50`    | Messages per campaign batch |

**Logic to apply:**
- `async_sending_mode = database` → push to `SendMessageJob` (queue)
- `async_sending_mode = instant`  → call API synchronously (blocks request)
- `msg_sending_mode = database`   → dispatch `ProcessCampaignBatchJob` per batch
- `msg_sending_mode = cronjob`    → write pending batch to DB, cron picks it up
- `campaign_batch_size`           → `array_chunk($recipients, $batchSize)` before batching

---

## Important Rules & Patterns

### ⚠️ FK Order: Always User Before Company
When creating a new company with an owner:
1. Create `User` first (no `company_id` yet)
2. Create `Company` with `user_id = $user->id`
3. Update `User` to set `company_id = $company->id`

Reversing steps 1 and 2 causes `SQLSTATE[23000]` FK constraint violation.

### .env Sync
`SettingsController::syncEnv()` reads `.env` line by line, replaces matching keys with regex, appends missing keys, writes the file, then runs `php artisan config:clear`. This means settings changes are live on the very next request.

### Storage
- **Local**: uploads go to `storage/app/public/uploads/` → served via `public/uploads/` symlink. Run `php artisan storage:link` once on setup.
- **S3 / Spaces**: requires `composer require league/flysystem-aws-s3-v3 "^3.0"`. Files go directly to your bucket, nothing stored on server.

### Pricing Plan Plugins
The `plugins` column stores comma-separated `app_key` values. The plan cards and table look up the live app names and icons from the `apps` table at render time. If `plugins` is empty/null = all apps are included.

---

## Environment File (`.env`)

The following keys are managed by Settings. Do not edit `.env` manually for these — use the Settings page instead:

```
APP_NAME          ← project_name
APP_URL           ← site_url
APP_DEBUG         ← app_debugging toggle
APP_TIMEZONE      ← timezone
FILESYSTEM_DISK   ← file_storage
AWS_ACCESS_KEY_ID ← aws_access_key
AWS_SECRET_ACCESS_KEY ← aws_secret_key
AWS_DEFAULT_REGION    ← aws_region
AWS_BUCKET            ← aws_bucket
AWS_URL               ← aws_bucket_url
PUSHER_APP_ID         ← pusher_app_id
PUSHER_APP_KEY        ← pusher_app_key
PUSHER_APP_SECRET     ← pusher_app_secret
PUSHER_APP_CLUSTER    ← pusher_app_cluster
BROADCAST_CONNECTION  ← broadcast_driver
```

The following should be set manually in `.env` (not managed by Settings):
```
APP_KEY, DB_*, SESSION_*, QUEUE_*, CACHE_*, MAIL_*, REDIS_*
```

---

## Changelog

| Date       | What was built                                                                  |
|------------|---------------------------------------------------------------------------------|
| 2026-03-26 | Project setup, DB connected to 157.180.4.221, migrations ran                   |
| 2026-03-26 | Login, Register, Dashboard pages with split layout + animated blobs             |
| 2026-03-26 | Companies page: table, drawer with 6 tabs, add modal, delete                    |
| 2026-03-26 | Pricing Plans page: cards + table, add modal with period toggle                 |
| 2026-03-26 | Apps page: free/premium sections, cards, add modal with icon preview            |
| 2026-03-26 | Period ENUM changed: monthly/annually → 1month/3months/6months/1year            |
| 2026-03-26 | Pricing plans plugins: replaced hardcoded chips with live apps from DB          |
| 2026-03-27 | Settings page: 5 tabs (System, Storage, Locale, Pusher, Logs)                  |
| 2026-03-27 | Settings → .env sync: save updates both DB and .env, clears config cache        |
| 2026-03-27 | App Logs: flat card list, color-coded by level, filter buttons + search         |
| 2026-03-27 | Activity Logs tab: coming soon placeholder                                       |
| 2026-03-27 | Storage section: local info box, S3 info box, composer command with copy button |
| 2026-03-27 | README.md and CLAUDE.md fully updated                                           |
| 2026-03-27 | App Logo upload (storage/app/public/uploads/, public/uploads symlink)           |
| 2026-03-27 | Header: logo only, user avatar initials, settings save toast popup              |
| 2026-03-27 | ✉️ Sending Settings tab: background mode, async mode, batch size (max 150)      |
