# FarmPilot.io — Laravel 12 marketing + SaaS foundation

Pre-launch teaser site, waitlist + newsletter engine, full CMS, store with
set-and-forget Stripe, and the domain scaffolding for `app.farmpilot.io` and
`api.farmpilot.io` — built for a **build-locally → upload manually** shared
hosting workflow.

## What's in the box

| Area | Details |
|---|---|
| **Teaser / landing** | Coming-soon homepage with waitlist capture, pre-seeded features & benefits, testimonials, pricing preview. A single **Coming soon mode** toggle in admin flips the site to launch mode later. |
| **Newsletter** | Double opt-in (toggleable), signed confirm/unsubscribe links, honeypot + rate limiting, campaigns composed in admin (Markdown), queued sending that works on shared-hosting cron. |
| **CMS** | Pages, blog (SEO fields, cover images, scheduling), testimonials, header/footer menu manager (drag-to-reorder), logo/favicon/OG-image upload, hero copy + feature blocks — all editable in the admin portal. |
| **Store** | One-off products with guest Stripe Checkout, subscription plans for launch. **Saving a product/plan auto-creates the Stripe Product + Price** (and issues a fresh Price when the amount changes, archiving the old one). No manual `price_...` IDs, ever. Orders recorded via webhook. |
| **Admin** | Filament 3 panel at `/admin` — Content / Marketing / Store / System groups, restricted to `is_admin` users. |
| **SEO & speed** | Per-page meta + OG + canonical, JSON-LD on posts, cached `sitemap.xml`, `robots.txt`, server-rendered Blade, cached menus/settings, Vite-built Tailwind 4, fonts via bunny.net. |
| **Domains** | `farmpilot.io` = marketing + admin, `app.farmpilot.io` = tenant portal shell, `api.farmpilot.io` = versioned API (also mounted at `/api/v1` for local dev). |

## Install

The Laravel 12 skeleton is already merged into this folder — no `composer create-project`
or overlay step needed.

**1. Build locally** (PHP 8.2+, Composer, Node 20+):

```bash
composer install --no-dev --optimize-autoloader   # generates vendor/ + autoload files
npm install && npm run build                      # produces public/build
```

**2a. Install with the web wizard** (recommended for shared hosting):

Upload everything (including `vendor/` and `public/build`, excluding `node_modules/`)
to the server, point the domain at `public/`, then open:

```
https://your-domain.com/install/
```

The wizard checks requirements + permissions, writes `.env` (with a generated
`APP_KEY`), tests the database connection, runs migrations + seeders, creates the
`storage` symlink and lets you set your own admin login. It locks itself afterwards
(`storage/app/.installed`) — **delete `public/install/` when done.**

**2b. Or install from the command line:**

```bash
cp .env.example .env        # then edit DB, SMTP, Stripe keys
php artisan key:generate
php artisan migrate --seed
php artisan storage:link
php artisan serve
```


## Stripe — genuinely set and forget

1. Put `STRIPE_KEY`, `STRIPE_SECRET`, `CASHIER_CURRENCY=aud` in `.env`.
2. Create/edit a **Product** or **Plan** in `/admin`. On save, `App\Services\StripeSync`
   creates the Stripe Product + Price and stores the IDs. Change the price and a
   new Stripe Price is issued automatically (Stripe prices are immutable); the
   old one is archived. The Stripe column in the admin tables shows sync state.
3. Add a webhook endpoint in the Stripe dashboard →
   `https://farmpilot.io/stripe/webhook`, events: at minimum
   `checkout.session.completed`, plus the [Cashier defaults](https://laravel.com/docs/12.x/billing#handling-stripe-webhooks)
   (`customer.subscription.*`, `invoice.*`). Put the signing secret in
   `STRIPE_WEBHOOK_SECRET`.
   - One-off purchases: guest Stripe Checkout from the store; the webhook records an **Order** you can see in admin.
   - Subscriptions: plans are already synced; at launch, sign-ups on `app.farmpilot.io` subscribe with `$user->newSubscription('default', $plan->stripe_price_id)->checkout(...)` — Cashier tables are already migrated and `User` is `Billable`.

If Stripe keys are absent (early dev), sync silently skips — nothing breaks.

## Newsletter flow

1. Any of the four capture forms (hero, CTA, footer, blog) hits `POST /subscribe`
   (throttled, honeypot-protected).
2. Double opt-in on by default (System → Site settings → Newsletter): the
   subscriber gets a signed confirm link; only **confirmed, not-unsubscribed**
   addresses are mailable.
3. Compose a campaign under Marketing → Newsletters (Markdown), hit **Send** —
   it's queued and drained by the scheduler. Every email carries a signed
   unsubscribe link.

## Shared hosting deployment (cPanel-style)

Build locally, upload the result. Never run `npm` on the server.

**1. Build**

```bash
composer install --no-dev --optimize-autoloader
npm run build                      # produces public/build
php artisan config:clear route:clear view:clear   # ship clean caches
```

**2. Upload** everything except `node_modules/`, `.git/`, `tests/` to a folder
*outside* the web root, e.g. `~/farmpilot/`.

**3. Point the three domains at `~/farmpilot/public`:**

- `farmpilot.io` — set the document root to `~/farmpilot/public` (or, if the
  host forces `public_html`, place a `public_html/.htaccess` that rewrites into
  it, or symlink `public_html → ~/farmpilot/public`).
- `app.farmpilot.io` and `api.farmpilot.io` — create each as a subdomain with
  **the same document root** `~/farmpilot/public`. Laravel's domain routing
  (see `bootstrap/app.php`) serves the right routes per host. Issue SSL
  (AutoSSL/Let's Encrypt) for all three.

**4. Server-side one-offs** (SSH or cPanel terminal):

```bash
cd ~/farmpilot
php artisan migrate --force
php artisan storage:link
php artisan optimize          # config + route + view caches
```

**5. Cron** (cPanel → Cron Jobs), one entry:

```
* * * * * /usr/local/bin/php /home/USER/farmpilot/artisan schedule:run >> /dev/null 2>&1
```

The scheduler runs `queue:work --stop-when-empty` each minute, which is what
drains newsletter sends and queued mail on hosts without long-running workers.

**6. Redeploys**: upload changed files, then `php artisan optimize:clear && php artisan optimize`
(and `migrate --force` when there are new migrations). `public/build` is fully
regenerated each `npm run build`, so upload that folder wholesale.

## Where things live

```
app/Filament/            Admin resources + Site settings page
app/Services/StripeSync  Auto product/price sync
app/Jobs/SendNewsletter  Campaign fan-out
app/Listeners/StripeEventListener   Orders from checkout webhooks
routes/web.php           Marketing site   routes/app.php  app.farmpilot.io
routes/api.php           /api/v1 + api.farmpilot.io root
resources/views/         Blade (SSR) + Tailwind 4 in resources/css/app.css
database/seeders/        All the pre-seeded launch content
```

## Roadmap hooks already in place

- `User` is `Billable` + Sanctum `HasApiTokens` — API keys and subscriptions ready.
- `routes/api.php` is versioned (`/v1`) with an auth\:sanctum group for device/telemetry endpoints.
- `app.farmpilot.io` route file is the mount point for the tenant portal (organisations → farms → devices → capabilities, per the platform plan).
- Reserved-slug list in `config/farmpilot.php` keeps the CMS from colliding with future routes.
