These docs are a work in progress and may not be fully up to date. Some pages may contain internal notes for our team.
Skip to Content

How to — run locally

Bring all three services up on your machine. End state: Dashboard at http://localhost:3000, Backend API at http://127.0.0.1:8000, Widget served from the Dashboard’s /api/ext/v2/snippet route (which reads the local ext/2/snippet.js artifact).

Prerequisites

  • PHP 8.2+ with the standard Laravel extensions
  • Composer
  • Node.js (LTS — Next.js 16 needs Node 20+; widget uses Vite 8)
  • npm
  • SQLite (default for the backend in dev; no separate DB server needed)
  • git with access to the three repos

The three repos must be siblings under /Users/codydavis/Local Sites/:

Local Sites/ ├── dropafinder-app-nextjs/ ├── dropafinder-app-external/ └── dropafinder-app-backend/

The dashboard’s snippet-serving route hardcodes the sibling path ../dropafinder-app-external/ext/2/snippet.{dev.,}js, so the layout matters.

Order of bring-up

Backend → Widget → Dashboard. The dashboard depends on the other two; bringing it up last avoids race-condition-shaped error spam.

Step 1 — Backend

cd "/Users/codydavis/Local Sites/dropafinder-app-backend" # First time only composer install cp .env.example .env php artisan key:generate php artisan jwt:secret # if not already set touch database/database.sqlite php artisan migrate --seed # Every time php artisan serve # serves on http://127.0.0.1:8000

In a second terminal, start the queue worker so AI imports + CSV jobs actually run:

php artisan queue:work --queue=aigen,default

Verify the API is up:

curl http://127.0.0.1:8000/api/users/me # Expected: 401 Unauthenticated (no token)

Backend env vars to fill in

The minimum set to get past php artisan serve:

  • APP_KEY — generated by php artisan key:generate
  • DB_CONNECTION=sqlite, DB_DATABASE=database/database.sqlite
  • JWT_SECRET — generated by php artisan jwt:secret

Optional (features that need them):

  • MAIL_* — for any flow that sends email (signup confirmations, invites)
  • PADDLE_API_BASE_URL and Paddle webhook secret — for billing flows
  • R2_* / AWS_* — for image upload
  • CDN_BASE_URL — for image CDN URLs
  • AI provider creds — for AI Import / Improvement jobs ([NEEDS CLARIFICATION] on which provider)

Common backend startup gotchas

  • SQLSTATE[HY000] on first request: you forgot php artisan migrate. Run it.
  • auth:api returns 500: JWT_SECRET not set. Run php artisan jwt:secret.
  • CORS error from the dashboard: config/cors.php is permissive (* paths, methods, origins) by default; if you tightened it, ensure http://localhost:3000 is allowed.

Step 2 — Widget

cd "/Users/codydavis/Local Sites/dropafinder-app-external/ext/2/source" # First time only npm install cp .env.development .env.local # if needed; defaults usually fine # Set VITE_API_APP_URL=http://127.0.0.1:8000/api in .env.local # Every time npm run dev # Vite dev server on default port

For most local work, you don’t actually need the widget’s dev server running — the Dashboard’s snippet route reads the built ext/2/snippet.js artifact, not the dev server. So:

# Build the widget once after every code change npm run build:dev # produces ext/2/snippet.dev.js # or npm run build # produces ext/2/snippet.js

The Dashboard’s snippet route at /api/ext/v2/snippet looks for snippet.dev.js first, falling back to snippet.js, falling back finally to the production CDN.

💡 Tip: If you’re not actively touching widget code, skip npm install here and let the dashboard pick up the last-built artifact (or fall through to the production CDN).

Step 3 — Dashboard

cd "/Users/codydavis/Local Sites/dropafinder-app-nextjs" # First time only npm install cp .env.local.example .env.local # if an example exists; otherwise create from scratch # Edit .env.local — see "Dashboard env vars" below # Every time npm run dev # http://localhost:3000

Dashboard env vars to fill in

NEXT_PUBLIC_API_URL=http://127.0.0.1:8000/api/ # Map provider keys (any one is enough to start; missing keys = silent fallback) NEXT_PUBLIC_GOOGLE_KEY= NEXT_PUBLIC_TOMTOM_KEY= NEXT_PUBLIC_MAPBOX_KEY= NEXT_PUBLIC_HERE_KEY= # Paddle (sandbox values for dev) NEXT_PUBLIC_PADDLE_ENVIRONMENT=sandbox NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=... NEXT_PUBLIC_PADDLE_BRONZE_PRODUCT_ID=... NEXT_PUBLIC_PADDLE_BRONZE_PRICE_ID=... NEXT_PUBLIC_PADDLE_PREMIUM_PRODUCT_ID=... NEXT_PUBLIC_PADDLE_PREMIUM_PRICE_ID=...

The map provider keys are optional for the dashboard’s own UI but required to render maps in Live Preview; without any, the preview will be empty or fall back to OSM.

Step 4 — End-to-end smoke

  1. Open http://localhost:3000
  2. Register an account
  3. Create a Location with a real address
  4. Create a Finder, attach the workspace’s default Map (or create one)
  5. Open the Finder Builder; the Live Preview rail should render the widget with your one location
  6. Copy the embed snippet from the Finders list
  7. Save the snippet HTML to a local file, open in a browser → finder should render

If the Live Preview is empty:

  • Check the browser console for fetch errors
  • Confirm npm run build:dev produced ext/2/snippet.dev.js
  • Confirm NEXT_PUBLIC_API_URL ends with a trailing /api/

If the standalone HTML embed is empty:

  • The CDN URL https://cdn.locationfinders.com/snippet.js is being hit, not your local build. To test against the local build, the embed page would need to load http://localhost:3000/api/ext/v2/snippet instead. (This is a dev-only configuration, not a customer-facing pattern.)

Useful commands

CommandWherePurpose
php artisan migrate:fresh --seedbackendWipe and reseed the SQLite DB
php artisan tinkerbackendREPL with Laravel context
php artisan queue:listen --queue=aigenbackendWatch the AI queue with reload
npm run lintnext.jsESLint pass
npm run buildnext.jsProduction build (occasionally useful for catching SSR issues)
npm run devwidget sourceVite dev server (rebuilds on save)
npm run build:devwidget sourceOne-shot dev build → snippet.dev.js

What is not set up locally

  • Cloudflare R2: image uploads in dev use the local storage/app/public disk by default. The widget’s CDN cache pattern doesn’t apply — the Dashboard’s snippet route reads the file directly.
  • Paddle: a real subscription flow won’t work without sandbox credentials; the Account → Billing page may render but checkout won’t complete.
  • Production CDN: the widget pulls finder data from origin (/ext/finders/details/{key}) in dev, since there’s nothing in R2.

Where to next