# Deploying to cPanel

The Builders Residence is a Laravel 13 application. cPanel shared hosting has no Node
runtime and often blocks symlinks, so this project is built to work around both: assets are
compiled locally and uploaded, and uploads are written to `public/uploads` rather than
through `storage:link`.

---

## Before you start — confirm with your host

Three things will stop the deployment dead if they are wrong. Check them first.

| Requirement | Why it matters |
|---|---|
| **PHP 8.3 or newer** (cPanel → MultiPHP Manager) | Laravel 13 requires it. On 8.2 or below the site will not boot at all. |
| **MySQL or MariaDB** | Standard on every cPanel plan. |
| `proc_open` and `exec` **not** in `disable_functions` | Artisan commands need them. If they are disabled, see [If artisan will not run](#if-artisan-will-not-run). |

Also confirm you can **set the domain's document root**, and whether your plan includes
**cron jobs** (needed for queued email — there is a fallback if not).

---

## 1. Build locally

Run these on your own machine, in the project folder. Nothing here runs on the server.

```bash
npm install && npm run build
```

```bash
composer install --no-dev --optimize-autoloader
```

`npm run build` writes `public/build/` — the compiled CSS, JavaScript, and the self-hosted
fonts. **This directory must be uploaded.** The site renders unstyled without it.

## 2. Upload

Either method works:

- **cPanel → Git Version Control** — clone the repository, then run the two build commands
  above locally and upload `public/build/` and `vendor/` separately (both are gitignored).
- **Zip and upload** — compress the whole project folder including `vendor/` and
  `public/build/`, upload through File Manager, and extract.

Do not upload `.env`, `node_modules/`, or `storage/logs/`.

## 3. Point the domain at `public/`

cPanel → **Domains** → edit your domain → set the document root to the project's `public`
directory, e.g. `/home/youruser/thebuildersresidence/public`.

This is the single most important step. If the document root points at the project root
instead, your `.env` file and source code become downloadable over the web.

## 4. Create the database

cPanel → **MySQL Databases**: create a database and a user, and grant the user
**all privileges** on it. Note the cPanel-prefixed names — they look like
`youruser_builders` and `youruser_admin`.

## 5. Configure `.env`

Copy `.env.example` to `.env` and edit:

```ini
APP_NAME="The Builders Residence"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://thebuildersresidence.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=youruser_builders
DB_USERNAME=youruser_admin
DB_PASSWORD=the-password-you-set

FILESYSTEM_DISK=public_uploads
QUEUE_CONNECTION=database

MAIL_MAILER=smtp
MAIL_HOST=mail.thebuildersresidence.com
MAIL_PORT=465
MAIL_SCHEME=smtps
MAIL_USERNAME=noreply@thebuildersresidence.com
MAIL_PASSWORD=the-mailbox-password
MAIL_FROM_ADDRESS="noreply@thebuildersresidence.com"

LEAD_NOTIFICATION_EMAIL="info@thebuildersresidence.com"

ADMIN_EMAIL=you@thebuildersresidence.com
ADMIN_PASSWORD=
```

`APP_DEBUG=false` is not optional. With it on, any error page displays your database
credentials to whoever triggered it.

Leave `ADMIN_PASSWORD` empty and the seeder generates a strong one and prints it **once**.
Copy it immediately — it is not stored anywhere and cannot be recovered.

## 6. Initialise

cPanel → **Terminal** (or SSH), from the project directory:

```bash
php artisan key:generate --force
```

```bash
php artisan migrate --force
```

```bash
php artisan db:seed --force
```

The seeder is idempotent and safe to re-run — it adds any new settings keys without
overwriting values you have edited. It does **not** create demo leads.

Then cache the configuration for speed:

```bash
php artisan config:cache && php artisan route:cache && php artisan view:cache
```

Re-run those three after any future `.env` change, or the old values stay live.

## 7. Permissions

```bash
chmod -R 755 storage bootstrap/cache public/uploads
```

If uploads fail with a permission error, these three directories are almost always why.

## 8. Cron jobs

cPanel → **Cron Jobs**. Add both, replacing the path with your own:

Queued email, every minute:

```bash
cd /home/youruser/thebuildersresidence && php artisan queue:work --stop-when-empty --max-time=55
```

Laravel's scheduler, every minute:

```bash
cd /home/youruser/thebuildersresidence && php artisan schedule:run
```

**If your plan has no cron jobs**, set `QUEUE_CONNECTION=sync` in `.env` instead. Email
then sends during the form submission — the visitor waits an extra second or two, which is
perfectly acceptable at this volume.

---

## After deploying — do these three things

1. **Sign in** at `https://yourdomain.com/admin/login` and change the generated password.
2. **Add the three WhatsApp invite links** in Settings → WhatsApp Groups. Until you do,
   people who complete the partner, investor, and consultation forms land on a thank-you
   page with no group to join. The dashboard warns you about this on the overview screen.
3. **Fill in Settings** — contact email, office address, phone, founder name, bio, and
   portrait. Then replace the placeholder developments and articles with real ones.

Then submit each of the four forms yourself and confirm the emails arrive.

---

## Troubleshooting

### The site loads but has no styling

`public/build/` was not uploaded. Run `npm run build` locally and upload that directory.

### 500 error with a blank page

Read `storage/logs/laravel.log`. The usual causes are a missing `APP_KEY`
(run `php artisan key:generate --force`), wrong database credentials, or permissions on
`storage/`.

### Changes to `.env` seem to have no effect

The config is cached. Run `php artisan config:clear`, then `php artisan config:cache`.

### Uploaded images do not appear

Check that `public/uploads` exists and is writable (`chmod -R 755 public/uploads`), and
that `FILESYSTEM_DISK=public_uploads` is set. This project deliberately avoids
`php artisan storage:link` — do not run it, it is not needed.

### Welcome emails land in spam

Shared-hosting IPs have mediocre sending reputation. Either add SPF and DKIM records for
your domain in cPanel → Email Deliverability, or point the `MAIL_*` settings at a
transactional provider's free tier. Both are configuration changes only — no code change.

### Emails are not sending at all

If `QUEUE_CONNECTION=database`, the queue cron is what actually delivers them. Check the
cron is running, then inspect the `jobs` table (pending) and `failed_jobs` (errored). To
test delivery immediately, set `QUEUE_CONNECTION=sync` temporarily.

### If artisan will not run

Some hosts disable `proc_open`/`exec`. Ask support to enable them for your account. If they
will not:

- Run the migrations by exporting your local database schema and importing it through
  **phpMyAdmin**.
- Generate an `APP_KEY` locally with `php artisan key:generate --show` and paste the value
  into `.env` by hand.
- Set `QUEUE_CONNECTION=sync`, since the queue worker cannot run.

---

## Updating the site later

```bash
npm run build && composer install --no-dev --optimize-autoloader
```

Upload the changed files, then:

```bash
php artisan migrate --force && php artisan config:cache && php artisan route:cache && php artisan view:cache
```

Content changes — developments, articles, settings, contact details — never require a
redeploy. That is what the dashboard is for.
