Multilingual (EN/FA/AR) marketing website — Laravel 11 · Inertia 2 · Vue 3 · TypeScript · Tailwind · Filament 3.
Built against the SahraMarketing Figma file, page User interface (1:2).
See docs/FIGMA-AUDIT.md for the full design audit and
docs/TRACEABILITY.md for the node → route → component → model → admin
resource mapping.
- PHP 8.2+
- Composer 2
- Node 20+
- MySQL 8 or PostgreSQL 14+
- A queue-capable cache (database driver works fine for development)
The fastest way to get the whole stack running — app, MariaDB, queue worker, and scheduler — with nothing but Docker installed:
cp .env.docker.example .env.docker
docker compose up -d --build
docker compose exec app php artisan db:seedThat's it — docker compose up -d --build builds the Vite assets and the PHP
image, waits for MariaDB to be healthy, runs migrations once (migrate
service), then starts:
app— Apache + PHP, published athttp://localhost:8000queue—php artisan queue:work, needed for contact-form/newsletter notification emails (QUEUE_CONNECTION=database)scheduler—php artisan schedule:work, promotes scheduled content every five minutes (seeroutes/console.php)db— MariaDB, data persisted in thedb_datanamed volume, forwarded to host port 3306
The env file is deliberately named .env.docker, not .env — this
project's real .env holds production cPanel secrets, and Docker Compose
auto-loads a file literally named .env for variable substitution, so
reusing that name here would risk mixing the two up. .env.docker.example
ships with a working APP_KEY and MAIL_MAILER=log, so no extra setup is
required before db:seed. Change ADMIN_EMAIL / ADMIN_PASSWORD in
.env.docker before seeding if this isn't a throwaway environment. Rebuild
after pulling code changes with docker compose up -d --build again;
docker compose logs -f app tails the web container.
This path bakes the app into an image (no bind-mounted source, no hot reload) — it's meant for bringing the project up to look at or demo, not for day-to-day development. For active development, use the native workflow below (§3–4), which supports Vite HMR.
composer install
npm install
cp .env.example .env
php artisan key:generate
# Point DB_* in .env at your database, then:
php artisan migrate
php artisan db:seed
php artisan storage:linkFetch the openly-licensed webfonts (Poppins + Vazirmatn fallback):
chmod +x scripts/fetch-fonts.sh
./scripts/fetch-fonts.shDoran FaNum (the Persian/Arabic typeface specified by the Figma variables) is commercially licensed and is not included. FA/AR render with Vazirmatn until you place the licensed files — see
docs/ASSET-MANIFEST.md§10. No code change is needed once they're in place.
Export the real images/logos/icons from Figma per docs/ASSET-MANIFEST.md,
then verify nothing is missing:
php artisan sahra:verify-assetscomposer run devThis runs the PHP server, queue listener, and Vite dev server together
(concurrently). Or run them separately:
php artisan serve
php artisan queue:listen
npm run devVisit http://localhost:8000 — it redirects to /en (or your browser's
preferred supported language).
Admin panel: http://localhost:8000/admin. The seeder creates an initial
admin account:
email: admin@sahra.example (or ADMIN_EMAIL in .env)
password: ChangeMe!2026 (or ADMIN_PASSWORD in .env)
Change this password immediately. Set ADMIN_EMAIL / ADMIN_PASSWORD /
ADMIN_NAME in .env before your first db:seed on a real environment to
avoid the default ever existing.
php artisan test # Pest: locale routing, publication states,
# forms, admin authorization
npm run typecheck # vue-tsc, strict mode
npm run test:e2e # Playwright — requires the app running;
# set BASE_URL if not http://localhost:8000composer install --no-dev --optimize-autoloader
npm run build
# Inertia SSR is built by the command above. Keep this process running in
# production so initial HTML contains the rendered page and SEO head.
php artisan inertia:start-ssr
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan migrate --forceQueue worker (contact-form email notifications run through the queue):
php artisan queue:work --tries=3Scheduler (promotes scheduled content, regenerates the sitemap — see
routes/console.php). Add one cron entry:
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
Environment checklist for production:
-
APP_ENV=production,APP_DEBUG=false -
APP_URLset to the real domain (used for canonical URLs, sitemaps, hreflang) -
SEO_CANONICAL_ORIGIN=https://sahramarketing.com(the non-www host) -
INERTIA_SSR_ENABLED=trueand the SSR process is supervised (Docker starts it automatically) -
SESSION_DRIVER,CACHE_STORE,QUEUE_CONNECTIONpointed at real backends (Redis recommended overdatabaseat scale) -
MAIL_*configured;CONTACT_NOTIFICATION_EMAILset to a monitored inbox -
ADMIN_EMAIL/ADMIN_PASSWORDset to real values before the first productiondb:seed - Doran FaNum licensed files placed in
public/fonts/doran/(optional but recommended for design fidelity) - All 44 assets in
docs/ASSET-MANIFEST.mdexported andphp artisan sahra:verify-assetspasses -
storage:linkrun sopublic/storageresolves
app/
Console/Commands/ Scheduled-publish + asset-verification commands
Contracts/ HasLocalisedSlugs (locale-aware route binding)
Enums/ PublicationStatus, SectionType, SubmissionStatus, MenuLocation
Filament/
Concerns/ HandlesTranslations — shared Create/Edit page logic
Pages/ ManageSettings
Resources/ One per admin-managed entity
Support/ TranslatableForm, PublicationFields
Http/
Controllers/ One per public route (see docs/TRACEABILITY.md)
Middleware/ SetLocale, RedirectToLocalisedRoute, HandleInertiaRequests
Requests/ ContactSubmissionRequest, NewsletterSubscriptionRequest
Models/ 22 Eloquent models + Translations/ (15 translation models)
Notifications/ ContactSubmissionReceived
Policies/ One per admin-managed entity, sharing BasePolicy
Providers/ AppServiceProvider, Filament/AdminPanelProvider
Services/ ContentTransformer, MediaTransformer, SeoBuilder, SubmissionHandler
Support/ SiteSettings, NavigationBuilder, LocaleAlternates
Traits/ HasTranslations, Publishable
config/locales.php Single source of truth for en/fa/ar
database/
migrations/ 20 migrations, chronological
seeders/ Real Figma copy, all 3 locales
factories/
docs/
FIGMA-AUDIT.md Full design audit + frame resolution
TRACEABILITY.md Node → route → component → model → resource
ASSET-MANIFEST.md Every image/icon/font with export instructions
RESPONSIVE-QA.md Per-page breakpoint decisions + QA checklist
IMPLEMENTATION-LOG.md Chronological build log + known limitations
resources/
css/ app.css (Tailwind layers), fonts.css (@font-face)
images/ Static SVGs (branding, clients, icons, decor)
js/
Components/ BrandLogo, LanguageSwitcher, SeoHead
Composables/ useTranslations
Layouts/ AppLayout, AppHeader, AppFooter, MobileMenu
Pages/ Inertia page components (see Known Limitations)
lib/motion.ts GSAP setup, lifecycle, reduced-motion
types/index.ts Shared TypeScript contracts
views/ app.blade.php (Inertia root), sitemap/*, filament/pages/*
routes/
web.php Locale-prefixed routes + bare-path redirect
console.php Scheduled commands
tests/
Feature/ Pest — Locale, Content, Forms, Admin
Browser/ Playwright — responsive.spec.ts
See docs/IMPLEMENTATION-LOG.md § Known limitations for the full list.
In short: dependencies were never installed in the authoring environment (no
network access there), Doran FaNum requires a separate licence, binary
assets are a manifest pending manual export, and the Vue page components
(resources/js/Pages/*.vue) are the next concrete unit of work — the full
data layer they'll consume (routes, controllers, transformers, seeded
content, TypeScript contracts) is complete and covered by tests today.