Compare commits
3 Commits
pricing-lo
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a6967dc01 | ||
|
|
257c09d178 | ||
|
|
f14006dc28 |
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/antfu
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/nuxt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/pinia
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/pnpm
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/slidev
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/tsdown
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/turborepo
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/unocss
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vite
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vitepress
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vitest
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue-best-practices
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue-router-best-practices
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue-testing-best-practices
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vueuse-functions
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/web-design-guidelines
|
|
||||||
50
.github/workflows/lint.yml
vendored
50
.github/workflows/lint.yml
vendored
@@ -1,50 +0,0 @@
|
|||||||
name: linter
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
- workos
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
- workos
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
quality:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: Testing
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: '8.4'
|
|
||||||
|
|
||||||
- name: Add Flux Credentials Loaded From ENV
|
|
||||||
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
|
|
||||||
npm install
|
|
||||||
|
|
||||||
- name: Run Pint
|
|
||||||
run: composer lint
|
|
||||||
|
|
||||||
# - name: Commit Changes
|
|
||||||
# uses: stefanzweifel/git-auto-commit-action@v7
|
|
||||||
# with:
|
|
||||||
# commit_message: fix code style
|
|
||||||
# commit_options: '--no-verify'
|
|
||||||
# file_pattern: |
|
|
||||||
# **/*
|
|
||||||
# !.github/workflows/*
|
|
||||||
60
.github/workflows/tests.yml
vendored
60
.github/workflows/tests.yml
vendored
@@ -1,60 +0,0 @@
|
|||||||
name: tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
- workos
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
- workos
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: Testing
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version: ['8.3', '8.4', '8.5']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php-version }}
|
|
||||||
tools: composer:v2
|
|
||||||
coverage: xdebug
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: '22'
|
|
||||||
|
|
||||||
- name: Install Node Dependencies
|
|
||||||
run: npm i
|
|
||||||
|
|
||||||
- name: Add Flux Credentials Loaded From ENV
|
|
||||||
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
|
||||||
|
|
||||||
- name: Copy Environment File
|
|
||||||
run: cp .env.example .env
|
|
||||||
|
|
||||||
- name: Generate Application Key
|
|
||||||
run: php artisan key:generate
|
|
||||||
|
|
||||||
- name: Build Assets
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Run Tests
|
|
||||||
run: ./vendor/bin/pest
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/antfu
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/nuxt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/pinia
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/pnpm
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/slidev
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/tsdown
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/turborepo
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/unocss
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vite
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vitepress
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vitest
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue-best-practices
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue-router-best-practices
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vue-testing-best-practices
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/vueuse-functions
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.agents/skills/web-design-guidelines
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
# SuperDesign Context Files for Fuel Price Application
|
|
||||||
|
|
||||||
This directory contains comprehensive design system documentation for the Fuel Price (Laravel Starter Kit) application.
|
|
||||||
|
|
||||||
## Generated Files
|
|
||||||
|
|
||||||
### 1. `init/components.md` (274 lines)
|
|
||||||
**Shared UI Primitives & Components**
|
|
||||||
|
|
||||||
Lists all reusable Blade components (7 total) with full source code:
|
|
||||||
- x-action-message: Temporary status messages
|
|
||||||
- x-app-logo: Branding component
|
|
||||||
- x-app-logo-icon: SVG logo icon
|
|
||||||
- x-auth-header: Centered auth page header
|
|
||||||
- x-auth-session-status: Session status display
|
|
||||||
- x-desktop-user-menu: User profile dropdown
|
|
||||||
- x-placeholder-pattern: Loading state pattern
|
|
||||||
|
|
||||||
Also documents Flux v2 components used throughout the application.
|
|
||||||
|
|
||||||
### 2. `init/layouts.md` (347 lines)
|
|
||||||
**Complete Layout Hierarchy**
|
|
||||||
|
|
||||||
Full content of 7 layout files:
|
|
||||||
- layouts/app.blade.php: Main app wrapper
|
|
||||||
- layouts/app/sidebar.blade.php: Authenticated layout with header/sidebar
|
|
||||||
- layouts/auth.blade.php: Auth wrapper
|
|
||||||
- layouts/auth/simple.blade.php: Centered auth layout
|
|
||||||
- layouts/auth/card.blade.php: Card-based auth layout
|
|
||||||
- layouts/auth/split.blade.php: Split-screen auth layout
|
|
||||||
- partials/head.blade.php: Shared head section
|
|
||||||
|
|
||||||
Includes usage patterns and color scheme documentation.
|
|
||||||
|
|
||||||
### 3. `init/routes.md` (120 lines)
|
|
||||||
**Route Configuration & Summary**
|
|
||||||
|
|
||||||
Complete files:
|
|
||||||
- routes/web.php: Public and authenticated routes
|
|
||||||
- routes/settings.php: Settings page routes
|
|
||||||
|
|
||||||
Plus detailed route summary table covering:
|
|
||||||
- 19 total routes (web + Fortify)
|
|
||||||
- Route grouping by protection level
|
|
||||||
- Livewire component routes mapping
|
|
||||||
|
|
||||||
### 4. `init/theme.md` (298 lines)
|
|
||||||
**Design Tokens & Configuration**
|
|
||||||
|
|
||||||
Complete content:
|
|
||||||
- resources/css/app.css: Tailwind imports and custom theme
|
|
||||||
- Color palette: Zinc neutral scale + semantic colors
|
|
||||||
- Typography: Instrument Sans font configuration
|
|
||||||
- Spacing system: 4px base unit scale
|
|
||||||
- Dark mode implementation
|
|
||||||
- Vite build configuration
|
|
||||||
- Flux theme setup
|
|
||||||
|
|
||||||
### 5. `init/pages.md` (291 lines)
|
|
||||||
**Page Dependency Trees**
|
|
||||||
|
|
||||||
Full-page component documentation with dependency trees:
|
|
||||||
1. StationSearch Livewire component (public)
|
|
||||||
2. Dashboard
|
|
||||||
3. Welcome/Home page
|
|
||||||
4. Settings pages (Profile, Security, Appearance)
|
|
||||||
5. Authentication pages (Login, Register, Password Reset, Email Verification, 2FA)
|
|
||||||
|
|
||||||
Includes data flow documentation and page transition patterns.
|
|
||||||
|
|
||||||
### 6. `init/extractable-components.md` (386 lines)
|
|
||||||
**Reusable Component Analysis**
|
|
||||||
|
|
||||||
16 components identified by category:
|
|
||||||
- 3 Form components (Search, Select, Loading Button)
|
|
||||||
- 6 Data Display components (Card, Stats, Legend)
|
|
||||||
- 2 Navigation components (Sidebar Nav, User Menu)
|
|
||||||
- 3 Layout components (Auth layouts)
|
|
||||||
- 2 Feedback components (Messages)
|
|
||||||
- 1 Map/Visualization component
|
|
||||||
- 2 Typography components
|
|
||||||
|
|
||||||
Includes reusability scoring table and extraction recommendations.
|
|
||||||
|
|
||||||
### 7. `init/design-system.md` (494 lines)
|
|
||||||
**Complete Design System**
|
|
||||||
|
|
||||||
Comprehensive design documentation:
|
|
||||||
- Brand identity & philosophy
|
|
||||||
- Color system: Zinc palette + semantic colors
|
|
||||||
- Typography: Typeface, hierarchy, weights
|
|
||||||
- Spacing & layout: Grid system, breakpoints
|
|
||||||
- Borders & radius: Border weight, color, radius scales
|
|
||||||
- Component patterns: Forms, Buttons, Cards, Navigation
|
|
||||||
- Data visualization: Map colors, marker styles
|
|
||||||
- Animations & transitions: Page transitions, component effects
|
|
||||||
- Accessibility: Contrast, interactive elements, typography
|
|
||||||
- Dark mode: Colors, images, implementation
|
|
||||||
- Responsive behavior: Breakpoints, layout adjustments
|
|
||||||
|
|
||||||
## Project Stack
|
|
||||||
|
|
||||||
- **Framework:** Laravel 11
|
|
||||||
- **UI Library:** Livewire 3 (classic components)
|
|
||||||
- **Styling:** Tailwind CSS v4
|
|
||||||
- **UI Components:** Flux UI v2
|
|
||||||
- **JavaScript:** Alpine.js
|
|
||||||
- **Font:** Instrument Sans (400, 500, 600 weights)
|
|
||||||
- **Map Library:** Leaflet
|
|
||||||
- **Map Tiles:** OpenStreetMap
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
- Dark-mode-first design with light mode support
|
|
||||||
- Responsive mobile-first architecture
|
|
||||||
- Comprehensive authentication UI (Fortify-based)
|
|
||||||
- Interactive fuel station search with Leaflet maps
|
|
||||||
- Settings/profile management
|
|
||||||
- Accessibility-focused component design
|
|
||||||
|
|
||||||
## Color Palette
|
|
||||||
|
|
||||||
### Primary
|
|
||||||
- Zinc neutral scale (50-950)
|
|
||||||
|
|
||||||
### Semantic
|
|
||||||
- Success: Green-500 (#22c55e)
|
|
||||||
- Warning: Amber-500 (#f59e0b)
|
|
||||||
- Error: Red-500 (#ef4444)
|
|
||||||
- Info: Slate-500 (#64748b)
|
|
||||||
|
|
||||||
## File Organization
|
|
||||||
|
|
||||||
All context files are in `init/` subdirectory for SuperDesign import.
|
|
||||||
|
|
||||||
Total: ~2,210 lines of comprehensive design documentation
|
|
||||||
|
|
||||||
Generated: April 6, 2026
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
# Shared UI Components
|
|
||||||
|
|
||||||
## Blade Components (Reusable)
|
|
||||||
|
|
||||||
### 1. `x-action-message`
|
|
||||||
**Path:** `resources/views/components/action-message.blade.php`
|
|
||||||
|
|
||||||
Displays a temporary status message that auto-hides after 2 seconds using Alpine.js.
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `on`: Event name to listen for (Livewire event)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Transition animation with fade-out
|
|
||||||
- Auto-hide timeout (2000ms)
|
|
||||||
- Default text: "Saved."
|
|
||||||
|
|
||||||
```blade
|
|
||||||
@props([
|
|
||||||
'on',
|
|
||||||
])
|
|
||||||
|
|
||||||
<div
|
|
||||||
x-data="{ shown: false, timeout: null }"
|
|
||||||
x-init="@this.on('{{ $on }}', () => { clearTimeout(timeout); shown = true; timeout = setTimeout(() => { shown = false }, 2000); })"
|
|
||||||
x-show.transition.out.opacity.duration.1500ms="shown"
|
|
||||||
x-transition:leave.opacity.duration.1500ms
|
|
||||||
style="display: none"
|
|
||||||
{{ $attributes->merge(['class' => 'text-sm']) }}
|
|
||||||
>
|
|
||||||
{{ $slot->isEmpty() ? __('Saved.') : $slot }}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. `x-app-logo`
|
|
||||||
**Path:** `resources/views/components/app-logo.blade.php`
|
|
||||||
|
|
||||||
Main app branding component. Renders as `flux:brand` (header) or `flux:sidebar.brand` (sidebar variant).
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `sidebar` (bool): If true, renders sidebar variant
|
|
||||||
- Inherits Flux component attributes
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Dynamic logo rendering based on context (header vs sidebar)
|
|
||||||
- Uses `x-app-logo-icon` for icon
|
|
||||||
- Passes through attributes to Flux components
|
|
||||||
|
|
||||||
```blade
|
|
||||||
@props([
|
|
||||||
'sidebar' => false,
|
|
||||||
])
|
|
||||||
|
|
||||||
@if($sidebar)
|
|
||||||
<flux:sidebar.brand name="Laravel Starter Kit" {{ $attributes }}>
|
|
||||||
<x-slot name="logo" class="flex aspect-square size-8 items-center justify-center rounded-md bg-accent-content text-accent-foreground">
|
|
||||||
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
|
|
||||||
</x-slot>
|
|
||||||
</flux:sidebar.brand>
|
|
||||||
@else
|
|
||||||
<flux:brand name="Laravel Starter Kit" {{ $attributes }}>
|
|
||||||
<x-slot name="logo" class="flex aspect-square size-8 items-center justify-center rounded-md bg-accent-content text-accent-foreground">
|
|
||||||
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
|
|
||||||
</x-slot>
|
|
||||||
</flux:brand>
|
|
||||||
@endif
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. `x-app-logo-icon`
|
|
||||||
**Path:** `resources/views/components/app-logo-icon.blade.php`
|
|
||||||
|
|
||||||
SVG icon for the application logo. Geometric design with fills.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Scalable SVG
|
|
||||||
- Uses `currentColor` for dynamic styling
|
|
||||||
- Can be sized with Tailwind classes
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 42" {{ $attributes }}>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M17.2 5.633 8.6.855 0 5.633v26.51l16.2 9 16.2-9v-8.442l7.6-4.223V9.856l-8.6-4.777-8.6 4.777V18.3l-5.6 3.111V5.633ZM38 18.301l-5.6 3.11v-6.157l5.6-3.11V18.3Zm-1.06-7.856-5.54 3.078-5.54-3.079 5.54-3.078 5.54 3.079ZM24.8 18.3v-6.157l5.6 3.111v6.158L24.8 18.3Zm-1 1.732 5.54 3.078-13.14 7.302-5.54-3.078 13.14-7.3v-.002Zm-16.2 7.89 7.6 4.222V38.3L2 30.966V7.92l5.6 3.111v16.892ZM8.6 9.3 3.06 6.222 8.6 3.143l5.54 3.08L8.6 9.3Zm21.8 15.51-13.2 7.334V38.3l13.2-7.334v-6.156ZM9.6 11.034l5.6-3.11v14.6l-5.6 3.11v-14.6Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. `x-auth-header`
|
|
||||||
**Path:** `resources/views/components/auth-header.blade.php`
|
|
||||||
|
|
||||||
Centered header for authentication pages with title and description.
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `title`: Main heading text
|
|
||||||
- `description`: Subheading/description text
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Centered layout
|
|
||||||
- Uses Flux typography components
|
|
||||||
|
|
||||||
```blade
|
|
||||||
@props([
|
|
||||||
'title',
|
|
||||||
'description',
|
|
||||||
])
|
|
||||||
|
|
||||||
<div class="flex w-full flex-col text-center">
|
|
||||||
<flux:heading size="xl">{{ $title }}</flux:heading>
|
|
||||||
<flux:subheading>{{ $description }}</flux:subheading>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. `x-auth-session-status`
|
|
||||||
**Path:** `resources/views/components/auth-session-status.blade.php`
|
|
||||||
|
|
||||||
Displays session status messages (typically success messages after redirect).
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `status`: Status message text (if present)
|
|
||||||
- Passes through attributes
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Conditional rendering (only shows if status exists)
|
|
||||||
- Green success styling
|
|
||||||
|
|
||||||
```blade
|
|
||||||
@props([
|
|
||||||
'status',
|
|
||||||
])
|
|
||||||
|
|
||||||
@if ($status)
|
|
||||||
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
|
|
||||||
{{ $status }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. `x-desktop-user-menu`
|
|
||||||
**Path:** `resources/views/components/desktop-user-menu.blade.php`
|
|
||||||
|
|
||||||
User profile dropdown menu with settings and logout options. Uses Flux components.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Sidebar profile with dropdown
|
|
||||||
- Shows user avatar, name, and email
|
|
||||||
- Menu items: Settings (cog icon), Logout (arrow icon)
|
|
||||||
- Logout form with CSRF protection
|
|
||||||
- Data test attributes for testing
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<flux:dropdown position="bottom" align="start">
|
|
||||||
<flux:sidebar.profile
|
|
||||||
:name="auth()->user()->name"
|
|
||||||
:initials="auth()->user()->initials()"
|
|
||||||
icon:trailing="chevrons-up-down"
|
|
||||||
data-test="sidebar-menu-button"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<flux:menu>
|
|
||||||
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
|
|
||||||
<flux:avatar
|
|
||||||
:name="auth()->user()->name"
|
|
||||||
:initials="auth()->user()->initials()"
|
|
||||||
/>
|
|
||||||
<div class="grid flex-1 text-start text-sm leading-tight">
|
|
||||||
<flux:heading class="truncate">{{ auth()->user()->name }}</flux:heading>
|
|
||||||
<flux:text class="truncate">{{ auth()->user()->email }}</flux:text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<flux:menu.separator />
|
|
||||||
<flux:menu.radio.group>
|
|
||||||
<flux:menu.item :href="route('profile.edit')" icon="cog" wire:navigate>
|
|
||||||
{{ __('Settings') }}
|
|
||||||
</flux:menu.item>
|
|
||||||
<form method="POST" action="{{ route('logout') }}" class="w-full">
|
|
||||||
@csrf
|
|
||||||
<flux:menu.item
|
|
||||||
as="button"
|
|
||||||
type="submit"
|
|
||||||
icon="arrow-right-start-on-rectangle"
|
|
||||||
class="w-full cursor-pointer"
|
|
||||||
data-test="logout-button"
|
|
||||||
>
|
|
||||||
{{ __('Log out') }}
|
|
||||||
</flux:menu.item>
|
|
||||||
</form>
|
|
||||||
</flux:menu.radio.group>
|
|
||||||
</flux:menu>
|
|
||||||
</flux:dropdown>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. `x-placeholder-pattern`
|
|
||||||
**Path:** `resources/views/components/placeholder-pattern.blade.php`
|
|
||||||
|
|
||||||
SVG placeholder pattern for skeleton/loading states. Diagonal line pattern.
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `id`: Unique pattern ID (auto-generated)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Reusable SVG pattern definition
|
|
||||||
- Can be styled with Tailwind stroke classes
|
|
||||||
- Used in dashboard placeholder cards
|
|
||||||
|
|
||||||
```blade
|
|
||||||
@props([
|
|
||||||
'id' => uniqid(),
|
|
||||||
])
|
|
||||||
|
|
||||||
<svg {{ $attributes }} fill="none">
|
|
||||||
<defs>
|
|
||||||
<pattern id="pattern-{{ $id }}" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
|
|
||||||
<path d="M-1 5L5 -1M3 9L8.5 3.5" stroke-width="0.5"></path>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
<rect stroke="none" fill="url(#pattern-{{ $id }})" width="100%" height="100%"></rect>
|
|
||||||
</svg>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Flux UI Components Used
|
|
||||||
|
|
||||||
Flux v2 components extensively used throughout the application:
|
|
||||||
|
|
||||||
- **Layout:** `flux:header`, `flux:sidebar`, `flux:main`, `flux:spacer`
|
|
||||||
- **Navigation:** `flux:navbar`, `flux:navbar.item`, `flux:sidebar.nav`, `flux:sidebar.item`, `flux:sidebar.group`
|
|
||||||
- **Forms:** `flux:input`, `flux:select`, `flux:checkbox`, `flux:button`
|
|
||||||
- **Typography:** `flux:heading`, `flux:subheading`, `flux:text`
|
|
||||||
- **Dropdowns/Menus:** `flux:dropdown`, `flux:menu`, `flux:menu.item`, `flux:menu.separator`
|
|
||||||
- **UI Elements:** `flux:badge`, `flux:avatar`, `flux:brand`, `flux:profile`
|
|
||||||
- **Other:** `flux:tooltip`, `flux:separator`, `flux:link`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Flux Custom Icons
|
|
||||||
|
|
||||||
Located in `resources/views/flux/icon/`:
|
|
||||||
- `layout-grid.blade.php`
|
|
||||||
- `folder-git-2.blade.php`
|
|
||||||
- `chevrons-up-down.blade.php`
|
|
||||||
- `book-open-text.blade.php`
|
|
||||||
|
|
||||||
These extend Flux's default icon library.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Alpine.js Data Objects
|
|
||||||
|
|
||||||
### `stationMap`
|
|
||||||
**Path:** `resources/js/maps/station-map.js`
|
|
||||||
|
|
||||||
Leaflet map integration for displaying fuel stations:
|
|
||||||
- Initializes map centered on UK
|
|
||||||
- Plots colored circle markers for stations
|
|
||||||
- Color coding: green (current), slate (recent), amber (stale), red (outdated)
|
|
||||||
- Popup display with station details
|
|
||||||
- Responsive bounds fitting
|
|
||||||
- Watches for data changes via Alpine
|
|
||||||
|
|
||||||
@@ -1,494 +0,0 @@
|
|||||||
# Design System
|
|
||||||
|
|
||||||
Comprehensive documentation of the design system, visual language, and brand identity for the Fuel Price application.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Brand Identity
|
|
||||||
|
|
||||||
### App Name
|
|
||||||
"Fuel Price" (Laravel Starter Kit internally)
|
|
||||||
|
|
||||||
### Logo
|
|
||||||
- **Style:** Geometric, modern
|
|
||||||
- **Colors:** Adapts to light/dark mode
|
|
||||||
- **Variants:** Header logo (wider), Sidebar logo (square icon)
|
|
||||||
- **Usage:** All pages in navigation
|
|
||||||
|
|
||||||
### Design Philosophy
|
|
||||||
- Clean, minimal aesthetic
|
|
||||||
- Dark-first design (dark mode as primary)
|
|
||||||
- Accessibility-focused
|
|
||||||
- Data visualization emphasis (maps, charts)
|
|
||||||
- Utility-oriented interface
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Color System
|
|
||||||
|
|
||||||
### Primary Palette - Zinc (Neutral)
|
|
||||||
|
|
||||||
Used as base colors for UI, text, and borders.
|
|
||||||
|
|
||||||
```
|
|
||||||
Zinc-50: #fafafa (Lightest backgrounds)
|
|
||||||
Zinc-100: #f5f5f5 (Light backgrounds)
|
|
||||||
Zinc-200: #e5e5e5 (Light borders, dividers)
|
|
||||||
Zinc-300: #d4d4d4 (Subtle borders)
|
|
||||||
Zinc-400: #a3a3a3 (Placeholder text)
|
|
||||||
Zinc-500: #737373 (Secondary text)
|
|
||||||
Zinc-600: #525252 (Body text)
|
|
||||||
Zinc-700: #404040 (Dark text)
|
|
||||||
Zinc-800: #262626 (Page backgrounds dark)
|
|
||||||
Zinc-900: #171717 (Header/sidebar backgrounds)
|
|
||||||
Zinc-950: #0a0a0a (Darkest)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Semantic Colors
|
|
||||||
|
|
||||||
#### Success
|
|
||||||
- **Primary:** `#22c55e` (Green-500)
|
|
||||||
- **Dark:** `#16a34a` (Green-600)
|
|
||||||
- **Light:** `#86efac` (Green-300)
|
|
||||||
- **Usage:** Success messages, positive indicators, current/fresh data
|
|
||||||
|
|
||||||
#### Warning
|
|
||||||
- **Primary:** `#f59e0b` (Amber-500)
|
|
||||||
- **Light:** `#fbbf24` (Amber-400)
|
|
||||||
- **Usage:** Stale data, deprecation notices
|
|
||||||
|
|
||||||
#### Error/Danger
|
|
||||||
- **Primary:** `#ef4444` (Red-500)
|
|
||||||
- **Dark:** `#dc2626` (Red-600)
|
|
||||||
- **Light:** `#fca5a5` (Red-300)
|
|
||||||
- **Usage:** Outdated data, error messages, destructive actions
|
|
||||||
|
|
||||||
#### Info
|
|
||||||
- **Primary:** `#64748b` (Slate-500)
|
|
||||||
- **Usage:** Neutral information, recent data
|
|
||||||
|
|
||||||
### Accent Colors
|
|
||||||
|
|
||||||
**Light Mode:**
|
|
||||||
- Accent: `--color-neutral-800` (dark gray)
|
|
||||||
- Accent Content: `--color-neutral-800`
|
|
||||||
- Accent Foreground: `--color-white`
|
|
||||||
|
|
||||||
**Dark Mode:**
|
|
||||||
- Accent: `--color-white`
|
|
||||||
- Accent Content: `--color-white`
|
|
||||||
- Accent Foreground: `--color-neutral-800`
|
|
||||||
|
|
||||||
Used for:
|
|
||||||
- Primary buttons
|
|
||||||
- Active navigation items
|
|
||||||
- Focus states
|
|
||||||
- Primary CTAs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Typography System
|
|
||||||
|
|
||||||
### Typeface
|
|
||||||
|
|
||||||
**Primary Font:** Instrument Sans
|
|
||||||
- **Source:** Google Fonts (fonts.bunny.net)
|
|
||||||
- **Weights Available:** 400 (Regular), 500 (Medium), 600 (Semibold)
|
|
||||||
- **Category:** Sans-serif, Humanist
|
|
||||||
- **Use Case:** All body text, headings, UI labels
|
|
||||||
|
|
||||||
**Fallback Stack:**
|
|
||||||
```
|
|
||||||
'Instrument Sans',
|
|
||||||
ui-sans-serif,
|
|
||||||
system-ui,
|
|
||||||
sans-serif,
|
|
||||||
'Apple Color Emoji',
|
|
||||||
'Segoe UI Emoji',
|
|
||||||
'Segoe UI Symbol',
|
|
||||||
'Noto Color Emoji'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Font Sizes & Hierarchy
|
|
||||||
|
|
||||||
Via Flux components (sizes managed by Flux):
|
|
||||||
|
|
||||||
- **flux:heading size="xl":** Page titles (largest)
|
|
||||||
- **flux:heading size="lg":** Section titles
|
|
||||||
- **flux:heading:** Standard headings
|
|
||||||
- **flux:subheading:** Secondary headings, descriptions
|
|
||||||
- **flux:text:** Body text
|
|
||||||
- **flux:label:** Form labels, captions
|
|
||||||
- **flux:badge:** Small labels, tags
|
|
||||||
|
|
||||||
### Line Heights
|
|
||||||
|
|
||||||
- **Tight:** Labels, badges (`leading-tight`)
|
|
||||||
- **Normal:** Body text, headings (default)
|
|
||||||
- **Relaxed:** Long-form content (when needed)
|
|
||||||
|
|
||||||
### Weight Distribution
|
|
||||||
|
|
||||||
- **400 (Regular):** Body text, regular content
|
|
||||||
- **500 (Medium):** Secondary headings, strong emphasis, labels
|
|
||||||
- **600 (Semibold):** Primary headings, buttons, strong emphasis
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Spacing & Layout
|
|
||||||
|
|
||||||
### Spacing Scale
|
|
||||||
|
|
||||||
Based on 4px base unit:
|
|
||||||
|
|
||||||
```
|
|
||||||
0: 0px
|
|
||||||
0.5: 2px
|
|
||||||
1: 4px
|
|
||||||
1.5: 6px
|
|
||||||
2: 8px
|
|
||||||
2.5: 10px
|
|
||||||
3: 12px
|
|
||||||
3.5: 14px
|
|
||||||
4: 16px
|
|
||||||
5: 20px
|
|
||||||
6: 24px
|
|
||||||
7: 28px
|
|
||||||
8: 32px
|
|
||||||
9: 36px
|
|
||||||
10: 40px
|
|
||||||
11: 44px
|
|
||||||
12: 48px
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Spacing Patterns
|
|
||||||
|
|
||||||
- **Gap between elements:** `gap-3` (12px), `gap-4` (16px), `gap-6` (24px)
|
|
||||||
- **Form field spacing:** `gap-2` (8px) for label + input
|
|
||||||
- **Card padding:** `p-4` (16px) for compact, `p-6` (24px) for default, `p-10` (40px) for large
|
|
||||||
- **Horizontal padding:** `px-4` (16px) standard
|
|
||||||
- **Vertical padding:** `py-3` (12px) standard
|
|
||||||
|
|
||||||
### Grid System
|
|
||||||
|
|
||||||
- Flex-based layouts (no CSS Grid for most components)
|
|
||||||
- Default gap: `gap-4` (16px)
|
|
||||||
- Settings page: 2-column on desktop (220px sidebar + flex content)
|
|
||||||
- Station results: 1-column list, single-level nesting
|
|
||||||
|
|
||||||
### Responsive Breakpoints
|
|
||||||
|
|
||||||
```
|
|
||||||
Default (mobile): 0px
|
|
||||||
sm: 640px (@media max-width: 639px = mobile)
|
|
||||||
md: 768px (tablet)
|
|
||||||
lg: 1024px (desktop)
|
|
||||||
xl: 1280px (large desktop)
|
|
||||||
2xl: 1536px (extra large)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Breakpoint Patterns:**
|
|
||||||
- `max-lg:` - Mobile and tablet
|
|
||||||
- `lg:` - Desktop and larger
|
|
||||||
- `max-md:` - Mobile only
|
|
||||||
- `md:` - Tablet and larger
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Border & Radius
|
|
||||||
|
|
||||||
### Border Weight
|
|
||||||
|
|
||||||
- **Default:** 1px (all borders)
|
|
||||||
- **Emphasis:** None (single weight used)
|
|
||||||
|
|
||||||
### Border Radius
|
|
||||||
|
|
||||||
- **Small:** `rounded-md` (6-8px) - Icons, small buttons, badges
|
|
||||||
- **Medium:** `rounded-lg` (8-10px) - Inputs, dropdowns
|
|
||||||
- **Large:** `rounded-xl` (12-16px) - Cards, modals, large components
|
|
||||||
- **None:** `rounded-none` - Rarely used
|
|
||||||
|
|
||||||
### Border Colors
|
|
||||||
|
|
||||||
**Light Mode:**
|
|
||||||
- Default border: `border-zinc-200`
|
|
||||||
- Focus ring: `ring-accent` (accent color)
|
|
||||||
|
|
||||||
**Dark Mode:**
|
|
||||||
- Default border: `border-zinc-700`
|
|
||||||
- Card/modal: `border-stone-800` or `border-neutral-800`
|
|
||||||
- Focus ring: `ring-accent` with `ring-offset-2` and `ring-offset-accent-foreground`
|
|
||||||
|
|
||||||
### Focus States
|
|
||||||
|
|
||||||
All interactive elements have:
|
|
||||||
- `ring-2 ring-accent` (focus ring)
|
|
||||||
- `ring-offset-2` (space between element and ring)
|
|
||||||
- `ring-offset-accent-foreground` (offset background color)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Component Patterns
|
|
||||||
|
|
||||||
### Form Patterns
|
|
||||||
|
|
||||||
All form elements follow consistent structure:
|
|
||||||
1. Label (top)
|
|
||||||
2. Input/Select/Textarea
|
|
||||||
3. Error message (bottom, conditional)
|
|
||||||
4. Helper text (optional)
|
|
||||||
|
|
||||||
**Spacing:** `gap-2` between label and input
|
|
||||||
|
|
||||||
**States:**
|
|
||||||
- Normal: default appearance
|
|
||||||
- Focus: ring around element
|
|
||||||
- Disabled: opacity/pointer-events disabled
|
|
||||||
- Error: red text below
|
|
||||||
|
|
||||||
### Button Patterns
|
|
||||||
|
|
||||||
**Variants:**
|
|
||||||
- `variant="primary"` - Primary action (accent color background)
|
|
||||||
- `variant="secondary"` - Secondary action
|
|
||||||
- `variant="danger"` - Destructive action (red)
|
|
||||||
|
|
||||||
**States:**
|
|
||||||
- Normal: clickable
|
|
||||||
- Hover: slight opacity increase
|
|
||||||
- Disabled: `disabled` attribute via `wire:loading.attr="disabled"`
|
|
||||||
- Loading: show spinner or text change
|
|
||||||
|
|
||||||
### Card Patterns
|
|
||||||
|
|
||||||
Standard card styling:
|
|
||||||
- Border: `border border-zinc-200 dark:border-zinc-700`
|
|
||||||
- Radius: `rounded-xl`
|
|
||||||
- Background: white (light) / dark (dark mode)
|
|
||||||
- Padding: `p-4` to `p-10` depending on content density
|
|
||||||
- Shadow: `shadow-xs` for subtle lift (optional)
|
|
||||||
|
|
||||||
### Navigation Patterns
|
|
||||||
|
|
||||||
**Sidebar Navigation:**
|
|
||||||
- `flux:sidebar.nav` wrapper
|
|
||||||
- `flux:sidebar.group` for sections (with heading)
|
|
||||||
- `flux:sidebar.item` for links
|
|
||||||
- Active state: current attribute
|
|
||||||
- Icon on left, text on right
|
|
||||||
|
|
||||||
**Navbar Navigation:**
|
|
||||||
- `flux:navbar` wrapper
|
|
||||||
- `flux:navbar.item` for links
|
|
||||||
- Icons only or icon + text
|
|
||||||
- Horizontal layout
|
|
||||||
- Tooltip support
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Data Visualization
|
|
||||||
|
|
||||||
### Station Search Map Colors (Leaflet)
|
|
||||||
|
|
||||||
Classification-based color coding for fuel price data freshness:
|
|
||||||
|
|
||||||
- **Current (< 24h):** `#22c55e` (Green-500)
|
|
||||||
- **Recent (24-48h):** `#64748b` (Slate-500)
|
|
||||||
- **Stale (2-5 days):** `#f59e0b` (Amber-500)
|
|
||||||
- **Outdated (5+ days):** `#ef4444` (Red-500)
|
|
||||||
|
|
||||||
### Map Markers
|
|
||||||
|
|
||||||
- **Shape:** Circle
|
|
||||||
- **Radius:** 9px
|
|
||||||
- **Stroke:** White, 2px weight
|
|
||||||
- **Fill:** Classification color
|
|
||||||
- **Opacity:** 85% (0.85)
|
|
||||||
- **Click:** Shows popup with station details
|
|
||||||
|
|
||||||
### Data Table Patterns
|
|
||||||
|
|
||||||
(No explicit tables in current design, but pattern would be):
|
|
||||||
- Striped rows (alternate bg)
|
|
||||||
- Hover states for interactivity
|
|
||||||
- Clear column alignment
|
|
||||||
- Sortable headers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Animations & Transitions
|
|
||||||
|
|
||||||
### Page Transitions
|
|
||||||
|
|
||||||
All navigation via `wire:navigate` (no full page reload):
|
|
||||||
- Seamless SPA-like experience
|
|
||||||
- Preserves scroll position
|
|
||||||
- No loading screen between pages
|
|
||||||
|
|
||||||
### Component Transitions
|
|
||||||
|
|
||||||
**Alpine.js Transitions:**
|
|
||||||
- Fade on message dismiss: `x-show.transition.out.opacity.duration.1500ms`
|
|
||||||
- Duration: 1500ms (1.5 seconds)
|
|
||||||
- Effect: Opacity fade
|
|
||||||
|
|
||||||
**Flux Built-in:**
|
|
||||||
- Dropdown/menu opens: instant or quick fade
|
|
||||||
- Sidebar collapse: smooth width transition
|
|
||||||
- Modal appears: fade + scale (typical Flux defaults)
|
|
||||||
|
|
||||||
### Hover & Active States
|
|
||||||
|
|
||||||
- Links: `hover:underline` (default Flux)
|
|
||||||
- Buttons: Opacity change on hover
|
|
||||||
- Navigation items: Background highlight on hover/active
|
|
||||||
- Interactive elements: Subtle color shift
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Accessibility
|
|
||||||
|
|
||||||
### Color Contrast
|
|
||||||
|
|
||||||
- Text on backgrounds meets WCAG AA standards (4.5:1 for normal text)
|
|
||||||
- Semantic colors (green/red) supplemented with icons/patterns
|
|
||||||
- No color-only indicators
|
|
||||||
|
|
||||||
### Interactive Elements
|
|
||||||
|
|
||||||
- Keyboard navigable (all Flux components)
|
|
||||||
- Focus indicators visible (`ring-2 ring-accent`)
|
|
||||||
- Touch-friendly sizing (minimum 44x44px recommended)
|
|
||||||
- ARIA labels where needed
|
|
||||||
|
|
||||||
### Typography
|
|
||||||
|
|
||||||
- Font sizes readable at standard distances
|
|
||||||
- Line height adequate for readability
|
|
||||||
- High contrast between text and background
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dark Mode
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
- **Method:** `class="dark"` on HTML root
|
|
||||||
- **Toggle:** Managed by `@fluxAppearance` directive
|
|
||||||
- **Detection:** Optional prefers-color-scheme integration
|
|
||||||
|
|
||||||
### Dark Mode Colors
|
|
||||||
|
|
||||||
**Text:**
|
|
||||||
- Primary: `text-zinc-100` (light gray)
|
|
||||||
- Secondary: `text-zinc-400` (medium gray)
|
|
||||||
- Disabled: `text-zinc-500` (darker gray)
|
|
||||||
|
|
||||||
**Backgrounds:**
|
|
||||||
- Page: `bg-zinc-800`
|
|
||||||
- Header/Sidebar: `bg-zinc-900`
|
|
||||||
- Cards: `bg-stone-950` (darker variant)
|
|
||||||
- Inputs: `bg-zinc-900`
|
|
||||||
|
|
||||||
**Borders:**
|
|
||||||
- Primary: `border-zinc-700`
|
|
||||||
- Secondary: `border-stone-800`
|
|
||||||
- Subtle: `border-neutral-800`
|
|
||||||
|
|
||||||
**Accents:**
|
|
||||||
- Primary accent inverts: white (instead of dark gray)
|
|
||||||
|
|
||||||
### Image/SVG Handling in Dark
|
|
||||||
|
|
||||||
- Logo icon color inverts via `fill-current` and `text-*` classes
|
|
||||||
- Patterns use opacity: `stroke-neutral-100/20` (dark) vs `stroke-gray-900/20` (light)
|
|
||||||
- Leaflet map: Default OSM colors (already dark-friendly)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Responsive Behavior
|
|
||||||
|
|
||||||
### Mobile-First Approach
|
|
||||||
|
|
||||||
Design starts at mobile (smallest), enhances at larger breakpoints.
|
|
||||||
|
|
||||||
### Key Breakpoints
|
|
||||||
|
|
||||||
**Mobile (0-639px):**
|
|
||||||
- Single column layouts
|
|
||||||
- Hamburger menu (sidebar collapses)
|
|
||||||
- Stacked form fields
|
|
||||||
- Full-width cards
|
|
||||||
|
|
||||||
**Tablet (640-1023px):**
|
|
||||||
- Narrower multi-column (if applicable)
|
|
||||||
- Touch-friendly spacing
|
|
||||||
- Condensed headers
|
|
||||||
|
|
||||||
**Desktop (1024px+):**
|
|
||||||
- Multi-column layouts
|
|
||||||
- Horizontal navigation
|
|
||||||
- Sidebar persistent
|
|
||||||
- Full feature set visible
|
|
||||||
|
|
||||||
### Layout Adjustments
|
|
||||||
|
|
||||||
- Header: Hidden navbar items on mobile (`max-lg:hidden`)
|
|
||||||
- Sidebar: Becomes mobile hamburger at lg breakpoint
|
|
||||||
- Forms: Stack vertically on mobile, horizontal on desktop
|
|
||||||
- Grids: 1 column mobile, 2-3 columns desktop
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Consistency Patterns
|
|
||||||
|
|
||||||
### Consistent Component Usage
|
|
||||||
|
|
||||||
- All buttons: `flux:button` (never raw `<button>`)
|
|
||||||
- All inputs: `flux:input` (with validation/errors)
|
|
||||||
- All selects: `flux:select`
|
|
||||||
- All headings: `flux:heading` (sizes: xl, lg, etc.)
|
|
||||||
|
|
||||||
### Consistent Spacing
|
|
||||||
|
|
||||||
- Inter-element gaps: multiples of 4px
|
|
||||||
- Card padding: consistent with gap sizes
|
|
||||||
- Consistent use of flexbox for alignment
|
|
||||||
|
|
||||||
### Consistent States
|
|
||||||
|
|
||||||
- Forms show errors below input in red
|
|
||||||
- Success messages fade out after 2 seconds
|
|
||||||
- Disabled states use opacity + pointer-events
|
|
||||||
- Loading states shown via spinner or text change
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Notes
|
|
||||||
|
|
||||||
### CSS Custom Properties
|
|
||||||
|
|
||||||
```css
|
|
||||||
--font-sans: 'Instrument Sans', ...
|
|
||||||
--color-accent: (theme-dependent)
|
|
||||||
--color-accent-content: (theme-dependent)
|
|
||||||
--color-accent-foreground: (theme-dependent)
|
|
||||||
--color-zinc-{50-950}: (full palette)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tailwind Integration
|
|
||||||
|
|
||||||
- Uses Tailwind CSS v4 with `@tailwindcss/vite`
|
|
||||||
- `@theme` block defines custom colors and fonts
|
|
||||||
- `@custom-variant` for dark mode
|
|
||||||
- `@layer` for CSS overrides
|
|
||||||
|
|
||||||
### Flux Integration
|
|
||||||
|
|
||||||
- Heavy reliance on Flux v2 components
|
|
||||||
- Flux provides theming, dark mode, layout components
|
|
||||||
- Custom CSS only overrides specific Flux defaults
|
|
||||||
- No Flux config file needed (uses defaults + CSS)
|
|
||||||
|
|
||||||
@@ -1,386 +0,0 @@
|
|||||||
# Extractable UI Components
|
|
||||||
|
|
||||||
This document identifies reusable UI components that could be extracted, packaged, and reused across other projects.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Form Components
|
|
||||||
|
|
||||||
### 1. Search Input with Debounce
|
|
||||||
**Source:** `resources/views/livewire/public/station-search.blade.php` (lines 8-16)
|
|
||||||
**Category:** Forms
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `name`: Input field name
|
|
||||||
- `wire:model`: Livewire property binding
|
|
||||||
- `label`: Display label
|
|
||||||
- `placeholder`: Placeholder text
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Flux form styling
|
|
||||||
- Wire model binding support
|
|
||||||
- Error message display
|
|
||||||
|
|
||||||
**Extractable:** Yes - Generic search input wrapper
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Multi-Option Select Dropdown
|
|
||||||
**Source:** `resources/views/livewire/public/station-search.blade.php` (lines 20-28, 34-42, 44-52)
|
|
||||||
**Category:** Forms
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `wire:model.live` or `wire:model`: Binding
|
|
||||||
- `name`: Field name
|
|
||||||
- `label`: Label text
|
|
||||||
- Options as slot
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Reactive updates via `wire:model.live`
|
|
||||||
- Error display
|
|
||||||
- Multiple select options
|
|
||||||
|
|
||||||
**Variants:**
|
|
||||||
- Fuel type selector (6 options)
|
|
||||||
- Radius selector (5 options)
|
|
||||||
- Sort selector (5 options)
|
|
||||||
|
|
||||||
**Extractable:** Yes - Reusable select component wrapper
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Form Submission Button with Loading State
|
|
||||||
**Source:** `resources/views/livewire/public/station-search.blade.php` (lines 54-59)
|
|
||||||
**Category:** Forms
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `type`: button type
|
|
||||||
- `variant`: "primary", etc.
|
|
||||||
- `wire:loading.attr`: Disabled state during loading
|
|
||||||
- `wire:target`: Target action
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Dynamic text based on loading state
|
|
||||||
- Wire loading indicators
|
|
||||||
- Primary variant styling
|
|
||||||
|
|
||||||
**Extractable:** Yes - Generic loading button component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Data Display Components
|
|
||||||
|
|
||||||
### 4. Station Result Card
|
|
||||||
**Source:** `resources/views/livewire/public/station-search.blade.php` (lines 94-128)
|
|
||||||
**Category:** Data Display
|
|
||||||
**Complexity:** Medium
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `station`: Object with station data
|
|
||||||
- `name`: Station name
|
|
||||||
- `address`: Street address
|
|
||||||
- `postcode`: Postcode
|
|
||||||
- `distance_km`: Distance in kilometers
|
|
||||||
- `price`: Fuel price in pence
|
|
||||||
- `price_classification`: One of (current, recent, stale, outdated)
|
|
||||||
- `price_classification_label`: Human label
|
|
||||||
- `price_updated_at`: ISO date string
|
|
||||||
- `is_supermarket`: Boolean
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Responsive layout (flex items-center justify-between)
|
|
||||||
- Color-coded price based on freshness
|
|
||||||
- Supermarket badge display
|
|
||||||
- Distance/address display
|
|
||||||
- Time-ago formatting
|
|
||||||
|
|
||||||
**Extractable:** Yes - Could be standalone component for displaying station data
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Results Count Summary
|
|
||||||
**Source:** `resources/views/livewire/public/station-search.blade.php` (lines 72-76)
|
|
||||||
**Category:** Data Display
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `count`: Total stations found
|
|
||||||
- `lowest_pence`: Lowest price in pence
|
|
||||||
- `avg_pence`: Average price in pence
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Formatted currency display
|
|
||||||
- Singular/plural handling
|
|
||||||
- Stats display on one line
|
|
||||||
|
|
||||||
**Extractable:** Yes - Generic stats summary component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Classification Legend/Color Code Guide
|
|
||||||
**Source:** `resources/views/livewire/public/station-search.blade.php` (lines 84-90)
|
|
||||||
**Category:** Data Display
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- Array of classification items with colors and labels
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Inline color swatches
|
|
||||||
- Legend item labels
|
|
||||||
- Responsive flex layout
|
|
||||||
|
|
||||||
**Extractable:** Yes - Reusable legend component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Navigation Components
|
|
||||||
|
|
||||||
### 7. Settings Sidebar Navigation
|
|
||||||
**Source:** `resources/views/pages/settings/layout.blade.php` (lines 3-7)
|
|
||||||
**Category:** Navigation
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- Navigation items array with:
|
|
||||||
- `label`: Display text
|
|
||||||
- `route`: Route name
|
|
||||||
- `icon`: Optional icon name
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- flux:navlist component
|
|
||||||
- wire:navigate support
|
|
||||||
- Active state detection
|
|
||||||
- Mobile/desktop responsive
|
|
||||||
|
|
||||||
**Extractable:** Yes - Generic sidebar nav for multi-section pages
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. User Profile Dropdown Menu
|
|
||||||
**Source:** `resources/views/components/desktop-user-menu.blade.php`
|
|
||||||
**Category:** Navigation
|
|
||||||
**Complexity:** Medium
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- User object with:
|
|
||||||
- `name`: User name
|
|
||||||
- `email`: User email
|
|
||||||
- `initials()`: Method to get initials
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Flux dropdown + menu
|
|
||||||
- Avatar display with initials
|
|
||||||
- Settings link
|
|
||||||
- Logout form with CSRF
|
|
||||||
- Test attributes for QA
|
|
||||||
|
|
||||||
**Extractable:** Yes - Standalone user menu component for authenticated apps
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Layout Components
|
|
||||||
|
|
||||||
### 9. Centered Authentication Layout Container
|
|
||||||
**Source:** `resources/views/layouts/auth/simple.blade.php`
|
|
||||||
**Category:** Layouts
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- None (slot-based)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Centered flex layout
|
|
||||||
- Max-width constraint (sm)
|
|
||||||
- Dark gradient background
|
|
||||||
- Logo link at top
|
|
||||||
- Responsive padding
|
|
||||||
|
|
||||||
**Extractable:** Yes - Generic centered auth layout
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 10. Card-Based Form Container
|
|
||||||
**Source:** `resources/views/layouts/auth/card.blade.php`
|
|
||||||
**Category:** Layouts
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- None (slot-based)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- White card on dark background
|
|
||||||
- Rounded borders with shadow
|
|
||||||
- Dark mode support
|
|
||||||
- Centered with max-width
|
|
||||||
- Padding inside card
|
|
||||||
|
|
||||||
**Extractable:** Yes - Modal/card wrapper component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 11. Split-Screen Auth Layout
|
|
||||||
**Source:** `resources/views/layouts/auth/split.blade.php`
|
|
||||||
**Category:** Layouts
|
|
||||||
**Complexity:** Medium
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- Quote display (generated from Inspiring::quotes)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Two-column grid (desktop only)
|
|
||||||
- Dark sidebar with quote/branding
|
|
||||||
- Form content on right
|
|
||||||
- Mobile-friendly (single column)
|
|
||||||
- Absolute background fill
|
|
||||||
|
|
||||||
**Extractable:** Yes - Premium auth layout component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status/Feedback Components
|
|
||||||
|
|
||||||
### 12. Temporary Action Message
|
|
||||||
**Source:** `resources/views/components/action-message.blade.php`
|
|
||||||
**Category:** Feedback
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `on`: Livewire event to listen to
|
|
||||||
- Slot for custom message (defaults to "Saved.")
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Alpine.js event listener
|
|
||||||
- Auto-hide after 2 seconds
|
|
||||||
- Fade transition
|
|
||||||
- Livewire integration
|
|
||||||
|
|
||||||
**Extractable:** Yes - Generic toast/message component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 13. Session Status Message
|
|
||||||
**Source:** `resources/views/components/auth-session-status.blade.php`
|
|
||||||
**Category:** Feedback
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `status`: Status message text
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Conditional rendering
|
|
||||||
- Green success styling
|
|
||||||
- Used in auth forms
|
|
||||||
|
|
||||||
**Extractable:** Yes - Simple status display component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Map/Visualization Components
|
|
||||||
|
|
||||||
### 14. Leaflet Map Integration
|
|
||||||
**Source:** `resources/js/maps/station-map.js`
|
|
||||||
**Category:** Map/Visualization
|
|
||||||
**Complexity:** High
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `results`: Array of station data with lat/lng/classifications
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Leaflet map initialization
|
|
||||||
- OpenStreetMap tiles
|
|
||||||
- Custom circle markers with colors
|
|
||||||
- Popup on marker click
|
|
||||||
- Auto-fit bounds
|
|
||||||
- Reactive to data changes
|
|
||||||
|
|
||||||
**Extractable:** Yes - Could be abstracted into reusable Leaflet component
|
|
||||||
|
|
||||||
**Note:** Currently tightly coupled to Alpine.js and station data structure. Would require prop mapping for reuse.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Typography/Header Components
|
|
||||||
|
|
||||||
### 15. Auth Page Header
|
|
||||||
**Source:** `resources/views/components/auth-header.blade.php`
|
|
||||||
**Category:** Typography
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `title`: Main heading
|
|
||||||
- `description`: Subheading
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Centered text
|
|
||||||
- Flux typography (heading + subheading)
|
|
||||||
- Used on all auth pages
|
|
||||||
|
|
||||||
**Extractable:** Yes - Generic header for centered pages
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 16. Settings Page Header
|
|
||||||
**Source:** `resources/views/partials/settings-heading.blade.php`
|
|
||||||
**Category:** Typography
|
|
||||||
**Complexity:** Low
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- None (hardcoded for settings)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Page title "Settings"
|
|
||||||
- Subheading text
|
|
||||||
- Separator line
|
|
||||||
|
|
||||||
**Extractable:** Maybe - Specific to settings but pattern is reusable
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Reusability Summary Table
|
|
||||||
|
|
||||||
| Component | Type | Complexity | Reusable | Dependencies | Extraction Cost |
|
|
||||||
|-----------|------|-----------|----------|---|---|
|
|
||||||
| Search Input | Form | Low | High | Flux | Very Low |
|
|
||||||
| Select Dropdown | Form | Low | High | Flux | Very Low |
|
|
||||||
| Loading Button | Form | Low | High | Flux, Livewire | Very Low |
|
|
||||||
| Station Card | Display | Medium | High | Flux | Low |
|
|
||||||
| Stats Summary | Display | Low | High | None | Very Low |
|
|
||||||
| Legend | Display | Low | High | Tailwind | Very Low |
|
|
||||||
| Settings Nav | Nav | Low | High | Flux | Very Low |
|
|
||||||
| User Menu | Nav | Medium | High | Flux, Auth | Low |
|
|
||||||
| Simple Auth Layout | Layout | Low | High | Tailwind | Very Low |
|
|
||||||
| Card Container | Layout | Low | High | Tailwind | Very Low |
|
|
||||||
| Split Layout | Layout | Medium | High | Tailwind | Low |
|
|
||||||
| Action Message | Feedback | Low | High | Alpine, Livewire | Very Low |
|
|
||||||
| Status Message | Feedback | Low | High | None | Very Low |
|
|
||||||
| Leaflet Map | Visualization | High | Medium | Leaflet, Alpine | Medium |
|
|
||||||
| Auth Header | Typography | Low | High | Flux | Very Low |
|
|
||||||
| Settings Header | Typography | Low | Medium | Flux | Very Low |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Top Candidates for Extraction
|
|
||||||
|
|
||||||
1. **Search Input Component** - Generic, simple, widely useful
|
|
||||||
2. **Station Card Component** - Good showcase of complex data display
|
|
||||||
3. **User Menu Component** - Authentication pattern, widely needed
|
|
||||||
4. **Loading Button Component** - Form UX pattern, commonly needed
|
|
||||||
5. **Simple Auth Layout** - Authentication flows are common
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Framework Package Recommendations
|
|
||||||
|
|
||||||
If extracting these components into a package:
|
|
||||||
|
|
||||||
1. Make them framework-agnostic (or have adapters)
|
|
||||||
2. Ensure Flux/Livewire are peer dependencies
|
|
||||||
3. Document required Alpine.js versions
|
|
||||||
4. Provide TypeScript declarations for props
|
|
||||||
5. Include Storybook examples
|
|
||||||
6. Consider CSS-in-JS vs Tailwind integration
|
|
||||||
|
|
||||||
@@ -1,347 +0,0 @@
|
|||||||
# Layout Files
|
|
||||||
|
|
||||||
## App Layouts (For Authenticated Users)
|
|
||||||
|
|
||||||
### 1. `layouts/app.blade.php` (Main App Layout)
|
|
||||||
**Path:** `resources/views/layouts/app.blade.php`
|
|
||||||
|
|
||||||
Wrapper layout that delegates to sidebar layout. Used by authenticated routes.
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<x-layouts::app.sidebar :title="$title ?? null">
|
|
||||||
<flux:main>
|
|
||||||
{{ $slot }}
|
|
||||||
</flux:main>
|
|
||||||
</x-layouts::app.sidebar>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. `layouts/app/sidebar.blade.php` (Sidebar Layout with Header/Navigation)
|
|
||||||
**Path:** `resources/views/layouts/app/sidebar.blade.php`
|
|
||||||
|
|
||||||
Core authenticated layout with sidebar navigation and header. Renders app header on desktop, mobile sidebar.
|
|
||||||
|
|
||||||
**Key Features:**
|
|
||||||
- Dark mode support (HTML class="dark")
|
|
||||||
- Responsive: Sidebar on desktop, hamburger on mobile
|
|
||||||
- Desktop navbar with Dashboard link
|
|
||||||
- Mobile header with sidebar toggle
|
|
||||||
- Search, Repository, and Documentation links
|
|
||||||
- User menu in header (desktop) and mobile sidebar
|
|
||||||
- Uses Flux components extensively
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
- HTML/head with partials.head include
|
|
||||||
- Body with min-h-screen, dark mode bg colors
|
|
||||||
- flux:header (desktop) with:
|
|
||||||
- Sidebar toggle (mobile only)
|
|
||||||
- App logo
|
|
||||||
- Desktop navbar with links
|
|
||||||
- Spacer
|
|
||||||
- Secondary navbar (Search, Repo, Docs icons)
|
|
||||||
- Desktop user menu
|
|
||||||
- flux:sidebar (mobile) with:
|
|
||||||
- App logo
|
|
||||||
- Sidebar collapse button
|
|
||||||
- Navigation group (Platform section)
|
|
||||||
- External links (Repo, Docs)
|
|
||||||
- flux:main slot for page content
|
|
||||||
- @fluxScripts directive at end
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
|
||||||
<head>
|
|
||||||
@include('partials.head')
|
|
||||||
</head>
|
|
||||||
<body class="min-h-screen bg-white dark:bg-zinc-800">
|
|
||||||
<flux:header container class="border-b border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
|
|
||||||
<flux:sidebar.toggle class="lg:hidden mr-2" icon="bars-2" inset="left" />
|
|
||||||
|
|
||||||
<x-app-logo href="{{ route('dashboard') }}" wire:navigate />
|
|
||||||
|
|
||||||
<flux:navbar class="-mb-px max-lg:hidden">
|
|
||||||
<flux:navbar.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
|
|
||||||
{{ __('Dashboard') }}
|
|
||||||
</flux:navbar.item>
|
|
||||||
</flux:navbar>
|
|
||||||
|
|
||||||
<flux:spacer />
|
|
||||||
|
|
||||||
<flux:navbar class="me-1.5 space-x-0.5 rtl:space-x-reverse py-0!">
|
|
||||||
<flux:tooltip :content="__('Search')" position="bottom">
|
|
||||||
<flux:navbar.item class="!h-10 [&>div>svg]:size-5" icon="magnifying-glass" href="#" :label="__('Search')" />
|
|
||||||
</flux:tooltip>
|
|
||||||
<flux:tooltip :content="__('Repository')" position="bottom">
|
|
||||||
<flux:navbar.item
|
|
||||||
class="h-10 max-lg:hidden [&>div>svg]:size-5"
|
|
||||||
icon="folder-git-2"
|
|
||||||
href="https://github.com/laravel/livewire-starter-kit"
|
|
||||||
target="_blank"
|
|
||||||
:label="__('Repository')"
|
|
||||||
/>
|
|
||||||
</flux:tooltip>
|
|
||||||
<flux:tooltip :content="__('Documentation')" position="bottom">
|
|
||||||
<flux:navbar.item
|
|
||||||
class="h-10 max-lg:hidden [&>div>svg]:size-5"
|
|
||||||
icon="book-open-text"
|
|
||||||
href="https://laravel.com/docs/starter-kits#livewire"
|
|
||||||
target="_blank"
|
|
||||||
:label="__('Documentation')"
|
|
||||||
/>
|
|
||||||
</flux:tooltip>
|
|
||||||
</flux:navbar>
|
|
||||||
|
|
||||||
<x-desktop-user-menu />
|
|
||||||
</flux:header>
|
|
||||||
|
|
||||||
<!-- Mobile Menu -->
|
|
||||||
<flux:sidebar collapsible="mobile" sticky class="lg:hidden border-e border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
|
|
||||||
<flux:sidebar.header>
|
|
||||||
<x-app-logo :sidebar="true" href="{{ route('dashboard') }}" wire:navigate />
|
|
||||||
<flux:sidebar.collapse class="in-data-flux-sidebar-on-desktop:not-in-data-flux-sidebar-collapsed-desktop:-mr-2" />
|
|
||||||
</flux:sidebar.header>
|
|
||||||
|
|
||||||
<flux:sidebar.nav>
|
|
||||||
<flux:sidebar.group :heading="__('Platform')">
|
|
||||||
<flux:sidebar.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
|
|
||||||
{{ __('Dashboard') }}
|
|
||||||
</flux:sidebar.item>
|
|
||||||
</flux:sidebar.group>
|
|
||||||
</flux:sidebar.nav>
|
|
||||||
|
|
||||||
<flux:spacer />
|
|
||||||
|
|
||||||
<flux:sidebar.nav>
|
|
||||||
<flux:sidebar.item icon="folder-git-2" href="https://github.com/laravel/livewire-starter-kit" target="_blank">
|
|
||||||
{{ __('Repository') }}
|
|
||||||
</flux:sidebar.item>
|
|
||||||
<flux:sidebar.item icon="book-open-text" href="https://laravel.com/docs/starter-kits#livewire" target="_blank">
|
|
||||||
{{ __('Documentation') }}
|
|
||||||
</flux:sidebar.item>
|
|
||||||
</flux:sidebar.nav>
|
|
||||||
</flux:sidebar>
|
|
||||||
|
|
||||||
{{ $slot }}
|
|
||||||
|
|
||||||
@fluxScripts
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auth Layouts (For Unauthenticated Users)
|
|
||||||
|
|
||||||
### 3. `layouts/auth.blade.php` (Auth Wrapper)
|
|
||||||
**Path:** `resources/views/layouts/auth.blade.php`
|
|
||||||
|
|
||||||
Simple wrapper that delegates to simple layout. Used for auth pages (login, register, password reset, etc.).
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<x-layouts::auth.simple :title="$title ?? null">
|
|
||||||
{{ $slot }}
|
|
||||||
</x-layouts::auth.simple>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. `layouts/auth/simple.blade.php` (Simple Auth Layout - Centered)
|
|
||||||
**Path:** `resources/views/layouts/auth/simple.blade.php`
|
|
||||||
|
|
||||||
Minimalist centered layout for authentication. Used for login, register, etc.
|
|
||||||
|
|
||||||
**Key Features:**
|
|
||||||
- Dark mode with gradient background (dark:from-neutral-950 to-neutral-900)
|
|
||||||
- Centered content with max-width constraint
|
|
||||||
- App logo centered with link to home
|
|
||||||
- Dark mode gradient background
|
|
||||||
- Flex column centered layout
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
|
||||||
<head>
|
|
||||||
@include('partials.head')
|
|
||||||
</head>
|
|
||||||
<body class="min-h-screen bg-white antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
|
||||||
<div class="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
|
||||||
<div class="flex w-full max-w-sm flex-col gap-2">
|
|
||||||
<a href="{{ route('home') }}" class="flex flex-col items-center gap-2 font-medium" wire:navigate>
|
|
||||||
<span class="flex h-9 w-9 mb-1 items-center justify-center rounded-md">
|
|
||||||
<x-app-logo-icon class="size-9 fill-current text-black dark:text-white" />
|
|
||||||
</span>
|
|
||||||
<span class="sr-only">{{ config('app.name', 'Laravel') }}</span>
|
|
||||||
</a>
|
|
||||||
<div class="flex flex-col gap-6">
|
|
||||||
{{ $slot }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@fluxScripts
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. `layouts/auth/card.blade.php` (Card-based Auth Layout)
|
|
||||||
**Path:** `resources/views/layouts/auth/card.blade.php`
|
|
||||||
|
|
||||||
Card-based authentication layout with rounded borders and shadow. Alternative to simple layout.
|
|
||||||
|
|
||||||
**Key Features:**
|
|
||||||
- Centered card with white background and dark border
|
|
||||||
- Max-width constraint
|
|
||||||
- Padding inside card (px-10 py-8)
|
|
||||||
- Light neutral-100 background
|
|
||||||
- Dark mode: stone-950 card bg with stone-800 border
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
|
||||||
<head>
|
|
||||||
@include('partials.head')
|
|
||||||
</head>
|
|
||||||
<body class="min-h-screen bg-neutral-100 antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
|
||||||
<div class="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
|
||||||
<div class="flex w-full max-w-md flex-col gap-6">
|
|
||||||
<a href="{{ route('home') }}" class="flex flex-col items-center gap-2 font-medium" wire:navigate>
|
|
||||||
<span class="flex h-9 w-9 items-center justify-center rounded-md">
|
|
||||||
<x-app-logo-icon class="size-9 fill-current text-black dark:text-white" />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="sr-only">{{ config('app.name', 'Laravel') }}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-6">
|
|
||||||
<div class="rounded-xl border bg-white dark:bg-stone-950 dark:border-stone-800 text-stone-800 shadow-xs">
|
|
||||||
<div class="px-10 py-8">{{ $slot }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@fluxScripts
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. `layouts/auth/split.blade.php` (Split-screen Auth Layout)
|
|
||||||
**Path:** `resources/views/layouts/auth/split.blade.php`
|
|
||||||
|
|
||||||
Split-screen layout with marketing content on left (desktop only) and form on right.
|
|
||||||
|
|
||||||
**Key Features:**
|
|
||||||
- Two-column layout on desktop (lg:grid-cols-2)
|
|
||||||
- Left side: Dark background (neutral-900) with motivational quote (hidden on mobile)
|
|
||||||
- Right side: Form content
|
|
||||||
- Logo centered on mobile, top-left on desktop (left side)
|
|
||||||
- Quote display with blockquote
|
|
||||||
- Uses Flux heading component
|
|
||||||
- Full viewport height (h-dvh)
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
|
||||||
<head>
|
|
||||||
@include('partials.head')
|
|
||||||
</head>
|
|
||||||
<body class="min-h-screen bg-white antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
|
||||||
<div class="relative grid h-dvh flex-col items-center justify-center px-8 sm:px-0 lg:max-w-none lg:grid-cols-2 lg:px-0">
|
|
||||||
<div class="bg-muted relative hidden h-full flex-col p-10 text-white lg:flex dark:border-e dark:border-neutral-800">
|
|
||||||
<div class="absolute inset-0 bg-neutral-900"></div>
|
|
||||||
<a href="{{ route('home') }}" class="relative z-20 flex items-center text-lg font-medium" wire:navigate>
|
|
||||||
<span class="flex h-10 w-10 items-center justify-center rounded-md">
|
|
||||||
<x-app-logo-icon class="me-2 h-7 fill-current text-white" />
|
|
||||||
</span>
|
|
||||||
{{ config('app.name', 'Laravel') }}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
@php
|
|
||||||
[$message, $author] = str(Illuminate\Foundation\Inspiring::quotes()->random())->explode('-');
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div class="relative z-20 mt-auto">
|
|
||||||
<blockquote class="space-y-2">
|
|
||||||
<flux:heading size="lg">“{{ trim($message) }}”</flux:heading>
|
|
||||||
<footer><flux:heading>{{ trim($author) }}</flux:heading></footer>
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-full lg:p-8">
|
|
||||||
<div class="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
|
||||||
<a href="{{ route('home') }}" class="z-20 flex flex-col items-center gap-2 font-medium lg:hidden" wire:navigate>
|
|
||||||
<span class="flex h-9 w-9 items-center justify-center rounded-md">
|
|
||||||
<x-app-logo-icon class="size-9 fill-current text-black dark:text-white" />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="sr-only">{{ config('app.name', 'Laravel') }}</span>
|
|
||||||
</a>
|
|
||||||
{{ $slot }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@fluxScripts
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Head/Partial Layouts
|
|
||||||
|
|
||||||
### 7. `partials/head.blade.php`
|
|
||||||
**Path:** `resources/views/partials/head.blade.php`
|
|
||||||
|
|
||||||
Shared head section included in all layouts.
|
|
||||||
|
|
||||||
**Content:**
|
|
||||||
- Meta charset and viewport
|
|
||||||
- Dynamic title generation
|
|
||||||
- Favicon setup (ico, svg, apple-touch-icon)
|
|
||||||
- Fonts: Instrument Sans from fonts.bunny.net (weights 400, 500, 600)
|
|
||||||
- Vite asset loading (CSS and JS)
|
|
||||||
- Flux appearance directive (dark mode toggle support)
|
|
||||||
|
|
||||||
```blade
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
|
|
||||||
<title>
|
|
||||||
{{ filled($title ?? null) ? $title.' - '.config('app.name', 'Laravel') : config('app.name', 'Laravel') }}
|
|
||||||
</title>
|
|
||||||
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
|
||||||
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />
|
|
||||||
|
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
||||||
@fluxAppearance
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage Pattern
|
|
||||||
|
|
||||||
| Route | Layout Chain | Use Case |
|
|
||||||
|-------|-----------|----------|
|
|
||||||
| `/` (home) | Custom (not using these layouts) | Landing page |
|
|
||||||
| `/dashboard` | `layouts/app` → `layouts/app/sidebar` | Authenticated user dashboard |
|
|
||||||
| `/login`, `/register` | `layouts/auth` → `layouts/auth/simple` | Simple auth forms |
|
|
||||||
| `/stations` | StationSearch Livewire component (no layout wrapper) | Public search page |
|
|
||||||
| `/settings/*` | `layouts/app` → `layouts/app/sidebar` | Settings pages |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Color Scheme in Layouts
|
|
||||||
|
|
||||||
- **Light Mode:** white background, zinc borders
|
|
||||||
- **Dark Mode:** zinc-800 body, zinc-900 header/sidebar, zinc-700 borders
|
|
||||||
- **Auth Pages (Dark):** linear gradient from neutral-950 to neutral-900
|
|
||||||
|
|
||||||
@@ -1,291 +0,0 @@
|
|||||||
# Pages & Full-Page Components
|
|
||||||
|
|
||||||
## Page Dependency Trees
|
|
||||||
|
|
||||||
Pages are organized by their view location and dependencies. Livewire components with full-page routes are documented here.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. StationSearch (Public Livewire Component)
|
|
||||||
**Route:** `/stations`
|
|
||||||
**Component:** `App\Livewire\Public\StationSearch`
|
|
||||||
**View:** `resources/views/livewire/public/station-search.blade.php`
|
|
||||||
|
|
||||||
### Dependency Tree
|
|
||||||
|
|
||||||
```
|
|
||||||
StationSearch (Livewire Component)
|
|
||||||
├── resources/views/livewire/public/station-search.blade.php
|
|
||||||
│ ├── flux:heading (component)
|
|
||||||
│ ├── flux:subheading (component)
|
|
||||||
│ ├── form > flux:input (Fuel location input)
|
|
||||||
│ ├── form > flux:select (Fuel type selector)
|
|
||||||
│ │ └── Options: Petrol, E5, Diesel, Premium Diesel, B10, HVO
|
|
||||||
│ ├── form > flux:select (Radius selector)
|
|
||||||
│ │ └── Options: 1, 2, 5, 10, 20 miles
|
|
||||||
│ ├── form > flux:select (Sort selector)
|
|
||||||
│ │ └── Options: Price, Distance, Updated, Brand, Reliable
|
|
||||||
│ ├── form > flux:button (Search button)
|
|
||||||
│ ├── Error display (conditional)
|
|
||||||
│ ├── Results meta (count, cheapest, average)
|
|
||||||
│ ├── x-data="stationMap(...)" (Alpine.js map)
|
|
||||||
│ │ └── resources/js/maps/station-map.js
|
|
||||||
│ │ ├── Leaflet map initialization
|
|
||||||
│ │ ├── OpenStreetMap tiles
|
|
||||||
│ │ └── Circle markers (station data)
|
|
||||||
│ ├── Legend (color codes for data age)
|
|
||||||
│ └── Results list
|
|
||||||
│ └── Station cards (name, address, price, distance, age classification)
|
|
||||||
│ └── flux:badge (Supermarket tag, conditional)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Flow
|
|
||||||
|
|
||||||
1. **PHP Class** (`App\Livewire\Public\StationSearch`):
|
|
||||||
- Properties: `$search`, `$fuelType`, `$radius`, `$sort`
|
|
||||||
- Properties: `$results[]`, `$meta[]`, `$apiError`
|
|
||||||
- Methods:
|
|
||||||
- `updatedFuelType()`: Refetch on fuel type change
|
|
||||||
- `updatedRadius()`: Refetch on radius change
|
|
||||||
- `updatedSort()`: Refetch on sort change
|
|
||||||
- `findStations()`: HTTP request to `/api/stations`
|
|
||||||
- `render()`: Returns view
|
|
||||||
|
|
||||||
2. **View Interactions**:
|
|
||||||
- `wire:model` on search input (two-way binding)
|
|
||||||
- `wire:model.live` on selectors (reactive updates)
|
|
||||||
- `wire:submit="findStations"` on form submit
|
|
||||||
- `wire:loading` for button state
|
|
||||||
- `wire:target="findStations"` for loading indicator
|
|
||||||
- `@entangle('results')` for Alpine.js map data
|
|
||||||
|
|
||||||
3. **JavaScript**:
|
|
||||||
- `stationMap(results)` Alpine data object
|
|
||||||
- Watches for `results` property changes
|
|
||||||
- Renders/updates Leaflet markers dynamically
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Dashboard
|
|
||||||
**Route:** `/dashboard`
|
|
||||||
**View:** `resources/views/dashboard.blade.php`
|
|
||||||
**Middleware:** `auth`, `verified`
|
|
||||||
|
|
||||||
### Dependency Tree
|
|
||||||
|
|
||||||
```
|
|
||||||
dashboard.blade.php
|
|
||||||
├── Layout: x-layouts::app (authenticated layout)
|
|
||||||
│ └── layouts/app.blade.php
|
|
||||||
│ └── x-layouts::app.sidebar (sidebar layout)
|
|
||||||
│ └── layouts/app/sidebar.blade.php
|
|
||||||
│ ├── partials/head.blade.php
|
|
||||||
│ ├── flux:header (navigation)
|
|
||||||
│ │ ├── x-app-logo
|
|
||||||
│ │ ├── flux:navbar with Dashboard link
|
|
||||||
│ │ ├── Secondary icons (Search, Repo, Docs)
|
|
||||||
│ │ └── x-desktop-user-menu
|
|
||||||
│ └── flux:sidebar (mobile navigation)
|
|
||||||
│
|
|
||||||
├── flux:main (page content wrapper)
|
|
||||||
│
|
|
||||||
└── Content: Grid of placeholder cards
|
|
||||||
└── x-placeholder-pattern (diagonal line SVG pattern)
|
|
||||||
└── 3 aspect-video cards (top row)
|
|
||||||
└── 1 flex-1 card (bottom, full height)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Placeholder cards for future dashboard widgets
|
|
||||||
- Responsive grid: 3 columns on desktop, stacked on mobile
|
|
||||||
- Dark mode support with adjusted stroke colors
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Welcome/Home Page
|
|
||||||
**Route:** `/`
|
|
||||||
**View:** `resources/views/welcome.blade.php`
|
|
||||||
|
|
||||||
### Note
|
|
||||||
|
|
||||||
The welcome view is large (>15KB) and contains:
|
|
||||||
- Full HTML with embedded Tailwind CSS
|
|
||||||
- Hero section with Fuel Price branding
|
|
||||||
- CTA buttons (Search Stations, View Source)
|
|
||||||
- Feature cards
|
|
||||||
- Dark mode support
|
|
||||||
- No layout wrapper (standalone)
|
|
||||||
|
|
||||||
(Full content available in actual file due to size)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Settings Pages
|
|
||||||
**Route:** `/settings/*`
|
|
||||||
**Layout:** `x-layouts::app` → `layouts/app/sidebar.blade.php`
|
|
||||||
|
|
||||||
### 4.1 Profile Settings
|
|
||||||
**Route:** `/settings/profile`
|
|
||||||
**Livewire Component:** `pages::settings.profile`
|
|
||||||
|
|
||||||
### Dependency Tree
|
|
||||||
|
|
||||||
```
|
|
||||||
settings/profile (Livewire Component)
|
|
||||||
├── Layout: x-layouts::app
|
|
||||||
│ └── layouts/app/sidebar.blade.php
|
|
||||||
│
|
|
||||||
├── partials/settings-heading.blade.php
|
|
||||||
│ ├── flux:heading ("Settings")
|
|
||||||
│ ├── flux:subheading ("Manage your profile...")
|
|
||||||
│ └── flux:separator
|
|
||||||
│
|
|
||||||
├── pages/settings/layout.blade.php (settings sidebar layout)
|
|
||||||
│ ├── flux:navlist (navigation)
|
|
||||||
│ │ ├── flux:navlist.item (Profile - current)
|
|
||||||
│ │ ├── flux:navlist.item (Security)
|
|
||||||
│ │ └── flux:navlist.item (Appearance)
|
|
||||||
│ │
|
|
||||||
│ └── Slot content (form fields)
|
|
||||||
│ └── Profile edit form (Livewire form)
|
|
||||||
│ ├── flux:input (Name)
|
|
||||||
│ ├── flux:input (Email)
|
|
||||||
│ ├── flux:button (Save)
|
|
||||||
│ └── Action messages on update
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Security Settings
|
|
||||||
**Route:** `/settings/security`
|
|
||||||
**Livewire Component:** `pages::settings.security`
|
|
||||||
|
|
||||||
- Two-factor authentication setup/management
|
|
||||||
- Recovery codes display
|
|
||||||
- Password confirmation (optional middleware)
|
|
||||||
|
|
||||||
### 4.3 Appearance Settings
|
|
||||||
**Route:** `/settings/appearance`
|
|
||||||
**Livewire Component:** `pages::settings.appearance`
|
|
||||||
|
|
||||||
- Theme selection (light/dark mode)
|
|
||||||
- Preference persistence
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Authentication Pages
|
|
||||||
**Layout:** `x-layouts::auth` → Various auth layouts
|
|
||||||
|
|
||||||
### 5.1 Login
|
|
||||||
**Route:** `/login`
|
|
||||||
**View:** `resources/views/pages/auth/login.blade.php`
|
|
||||||
**Layout:** `layouts/auth/simple.blade.php`
|
|
||||||
|
|
||||||
### Dependency Tree
|
|
||||||
|
|
||||||
```
|
|
||||||
login.blade.php
|
|
||||||
├── Layout: x-layouts::auth
|
|
||||||
│ └── layouts/auth/simple.blade.php
|
|
||||||
│ ├── partials/head.blade.php
|
|
||||||
│ └── Centered login form
|
|
||||||
│
|
|
||||||
├── x-auth-header (title + description)
|
|
||||||
├── x-auth-session-status (success message display)
|
|
||||||
├── form (POST to login.store)
|
|
||||||
│ ├── csrf token
|
|
||||||
│ ├── flux:input (Email)
|
|
||||||
│ ├── flux:input (Password, viewable)
|
|
||||||
│ │ └── flux:link to password.request (forgot password)
|
|
||||||
│ ├── flux:checkbox (Remember me)
|
|
||||||
│ └── flux:button (Log in, primary)
|
|
||||||
│
|
|
||||||
└── Sign up link (flux:link to register)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 Register
|
|
||||||
**Route:** `/register`
|
|
||||||
**View:** `resources/views/pages/auth/register.blade.php`
|
|
||||||
**Layout:** `layouts/auth/simple.blade.php`
|
|
||||||
|
|
||||||
```
|
|
||||||
register.blade.php
|
|
||||||
├── Layout: layouts/auth/simple.blade.php
|
|
||||||
├── x-auth-header
|
|
||||||
├── x-auth-session-status
|
|
||||||
├── form (POST to register.store)
|
|
||||||
│ ├── flux:input (Name)
|
|
||||||
│ ├── flux:input (Email)
|
|
||||||
│ ├── flux:input (Password, viewable)
|
|
||||||
│ ├── flux:input (Password Confirmation, viewable)
|
|
||||||
│ └── flux:button (Create account, primary)
|
|
||||||
└── Log in link
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 Password Reset
|
|
||||||
**Route:** `/forgot-password` and `/reset-password/{token}`
|
|
||||||
**Views:** `pages/auth/forgot-password.blade.php`, `pages/auth/reset-password.blade.php`
|
|
||||||
**Layout:** `layouts/auth/simple.blade.php`
|
|
||||||
|
|
||||||
### 5.4 Email Verification
|
|
||||||
**Route:** `/verify-email`
|
|
||||||
**View:** `pages/auth/verify-email.blade.php`
|
|
||||||
**Layout:** `layouts/auth/simple.blade.php`
|
|
||||||
|
|
||||||
### 5.5 Two-Factor Challenge
|
|
||||||
**Route:** `/two-factor-challenge`
|
|
||||||
**View:** `pages/auth/two-factor-challenge.blade.php`
|
|
||||||
**Layout:** `layouts/auth/simple.blade.php`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Component Include Hierarchy
|
|
||||||
|
|
||||||
### Layout Chain (All Authenticated Pages)
|
|
||||||
|
|
||||||
```
|
|
||||||
Page View
|
|
||||||
└── x-layouts::app (wrapper)
|
|
||||||
└── x-layouts::app.sidebar (main layout)
|
|
||||||
├── partials/head (in <head>)
|
|
||||||
│ ├── CSS imports (Vite)
|
|
||||||
│ ├── @fluxAppearance
|
|
||||||
│ └── Font preload
|
|
||||||
│
|
|
||||||
├── flux:header (desktop navigation)
|
|
||||||
│ ├── x-app-logo
|
|
||||||
│ ├── x-desktop-user-menu
|
|
||||||
│ └── flux:navbar / flux:tooltip components
|
|
||||||
│
|
|
||||||
├── flux:sidebar (mobile navigation)
|
|
||||||
│ ├── x-app-logo (:sidebar="true")
|
|
||||||
│ └── Navigation items
|
|
||||||
│
|
|
||||||
└── flux:main (page content slot)
|
|
||||||
└── Page-specific content
|
|
||||||
```
|
|
||||||
|
|
||||||
### Layout Chain (Auth Pages)
|
|
||||||
|
|
||||||
```
|
|
||||||
Auth Page View
|
|
||||||
└── x-layouts::auth (wrapper)
|
|
||||||
└── x-layouts::auth.simple (centered layout)
|
|
||||||
├── partials/head (in <head>)
|
|
||||||
└── Centered card with form
|
|
||||||
├── x-app-logo-icon
|
|
||||||
└── x-auth-header or form content
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Page Transitions
|
|
||||||
|
|
||||||
All pages use `wire:navigate` for Livewire navigation (no full page reload).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- `wire:navigate` on `<flux:brand>` links
|
|
||||||
- `wire:navigate` on `<flux:link>` components
|
|
||||||
- `wire:navigate` on navigation items
|
|
||||||
|
|
||||||
This enables seamless SPA-like experience.
|
|
||||||
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
# Routes
|
|
||||||
|
|
||||||
## Public Routes
|
|
||||||
|
|
||||||
### `routes/web.php`
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
use App\Livewire\Public\StationSearch;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
|
|
||||||
Route::view('/', 'homepage')->name('home');
|
|
||||||
|
|
||||||
Route::get('/stations', StationSearch::class)->name('stations.search');
|
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
|
||||||
Route::view('dashboard', 'dashboard')->name('dashboard');
|
|
||||||
});
|
|
||||||
|
|
||||||
require __DIR__.'/settings.php';
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Settings Routes
|
|
||||||
|
|
||||||
### `routes/settings.php`
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
use Laravel\Fortify\Features;
|
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function () {
|
|
||||||
Route::redirect('settings', 'settings/profile');
|
|
||||||
|
|
||||||
Route::livewire('settings/profile', 'pages::settings.profile')->name('profile.edit');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
|
||||||
Route::livewire('settings/appearance', 'pages::settings.appearance')->name('appearance.edit');
|
|
||||||
|
|
||||||
Route::livewire('settings/security', 'pages::settings.security')
|
|
||||||
->middleware(
|
|
||||||
when(
|
|
||||||
Features::canManageTwoFactorAuthentication()
|
|
||||||
&& Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'),
|
|
||||||
['password.confirm'],
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
->name('security.edit');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Auth routes (login, register, password reset, etc.) are provided by Laravel Fortify. See `config/fortify.php` for route definitions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Route Summary Table
|
|
||||||
|
|
||||||
| Method | Path | Name | Component/View | Middleware | Purpose |
|
|
||||||
|--------|------|------|---|---|---|
|
|
||||||
| GET | `/` | `home` | `welcome` view | public | Landing page |
|
|
||||||
| GET | `/stations` | `stations.search` | `StationSearch` Livewire | public | Fuel station search page |
|
|
||||||
| GET | `/dashboard` | `dashboard` | `dashboard` view | `auth`, `verified` | Authenticated user dashboard |
|
|
||||||
| GET/POST | `/login` | `login` | Fortify auth | public | User login |
|
|
||||||
| GET/POST | `/register` | `register` | Fortify auth | public | User registration |
|
|
||||||
| POST | `/logout` | `logout` | Fortify action | `auth` | Logout action |
|
|
||||||
| GET/POST | `/forgot-password` | `password.request` | Fortify auth | public | Password reset request |
|
|
||||||
| GET/POST | `/reset-password/{token}` | `password.reset` | Fortify auth | public | Password reset form |
|
|
||||||
| GET/POST | `/confirm-password` | `password.confirm` | Fortify auth | `auth` | Confirm password (before sensitive actions) |
|
|
||||||
| GET/POST | `/verify-email` | `verification.notice` | Fortify auth | `auth` | Email verification |
|
|
||||||
| GET | `/verify-email/{id}/{hash}` | `verification.verify` | Fortify action | `auth`, `signed` | Verify email action |
|
|
||||||
| GET/POST | `/two-factor-challenge` | `two-factor.login` | Fortify auth | `guest` | Two-factor challenge |
|
|
||||||
| GET | `/settings` | (redirect) | → `settings.profile` | `auth` | Redirect to profile |
|
|
||||||
| GET | `/settings/profile` | `profile.edit` | Livewire `pages::settings.profile` | `auth` | Edit user profile |
|
|
||||||
| GET | `/settings/appearance` | `appearance.edit` | Livewire `pages::settings.appearance` | `auth`, `verified` | Edit appearance preferences |
|
|
||||||
| GET | `/settings/security` | `security.edit` | Livewire `pages::settings.security` | `auth`, `verified`, (optional) `password.confirm` | Edit security settings |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Route Grouping
|
|
||||||
|
|
||||||
### Public Routes
|
|
||||||
- Landing page (`/`)
|
|
||||||
- Station search (`/stations`)
|
|
||||||
- Fortify auth routes (login, register, password reset, etc.)
|
|
||||||
|
|
||||||
### Authenticated Routes
|
|
||||||
- Dashboard (`/dashboard`)
|
|
||||||
- Settings pages (`/settings/*`)
|
|
||||||
|
|
||||||
### Settings Routes (Special Handling)
|
|
||||||
- Profile editing: `auth` only
|
|
||||||
- Appearance: `auth`, `verified`
|
|
||||||
- Security: `auth`, `verified`, optional password confirmation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Livewire Component Routes
|
|
||||||
|
|
||||||
| Route | Livewire Component | View | Purpose |
|
|
||||||
|-------|---|---|---|
|
|
||||||
| `/stations` | `App\Livewire\Public\StationSearch` | `livewire.public.station-search` | Interactive fuel station search with map |
|
|
||||||
| `/settings/profile` | `pages::settings.profile` | `pages.settings.profile` | User profile management |
|
|
||||||
| `/settings/appearance` | `pages::settings.appearance` | `pages.settings.appearance` | Theme/appearance preferences |
|
|
||||||
| `/settings/security` | `pages::settings.security` | `pages.settings.security` | Security and 2FA settings |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- **Fortify**: The application uses Laravel Fortify for authentication scaffolding. Auth routes are auto-registered by Fortify package.
|
|
||||||
- **Livewire Routes**: Routes using `Route::livewire()` expect corresponding Livewire components in `app/Livewire/`.
|
|
||||||
- **Middleware Chain**: Routes are protected with `auth` and/or `verified` middleware as appropriate.
|
|
||||||
- **Settings Redirect**: Accessing `/settings` redirects to `/settings/profile` (first settings tab).
|
|
||||||
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
# Design System & Theme
|
|
||||||
|
|
||||||
## Tailwind Configuration
|
|
||||||
|
|
||||||
### `tailwind.config.js`
|
|
||||||
|
|
||||||
File doesn't exist. The project uses Tailwind CSS v4 with `@tailwindcss/vite` plugin for compilation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CSS Configuration
|
|
||||||
|
|
||||||
### `resources/css/app.css`
|
|
||||||
|
|
||||||
```css
|
|
||||||
@import 'leaflet/dist/leaflet.css';
|
|
||||||
@import 'tailwindcss';
|
|
||||||
@import '../../vendor/livewire/flux/dist/flux.css';
|
|
||||||
|
|
||||||
@source '../views';
|
|
||||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
|
||||||
@source '../../vendor/livewire/flux-pro/stubs/**/*.blade.php';
|
|
||||||
@source '../../vendor/livewire/flux/stubs/**/*.blade.php';
|
|
||||||
|
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
|
||||||
|
|
||||||
@theme {
|
|
||||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
|
||||||
|
|
||||||
--color-zinc-50: #fafafa;
|
|
||||||
--color-zinc-100: #f5f5f5;
|
|
||||||
--color-zinc-200: #e5e5e5;
|
|
||||||
--color-zinc-300: #d4d4d4;
|
|
||||||
--color-zinc-400: #a3a3a3;
|
|
||||||
--color-zinc-500: #737373;
|
|
||||||
--color-zinc-600: #525252;
|
|
||||||
--color-zinc-700: #404040;
|
|
||||||
--color-zinc-800: #262626;
|
|
||||||
--color-zinc-900: #171717;
|
|
||||||
--color-zinc-950: #0a0a0a;
|
|
||||||
|
|
||||||
--color-accent: var(--color-neutral-800);
|
|
||||||
--color-accent-content: var(--color-neutral-800);
|
|
||||||
--color-accent-foreground: var(--color-white);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer theme {
|
|
||||||
.dark {
|
|
||||||
--color-accent: var(--color-white);
|
|
||||||
--color-accent-content: var(--color-white);
|
|
||||||
--color-accent-foreground: var(--color-neutral-800);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
|
|
||||||
*,
|
|
||||||
::after,
|
|
||||||
::before,
|
|
||||||
::backdrop,
|
|
||||||
::file-selector-button {
|
|
||||||
border-color: var(--color-gray-200, currentColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-flux-field]:not(ui-radio, ui-checkbox) {
|
|
||||||
@apply grid gap-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-flux-label] {
|
|
||||||
@apply !mb-0 !leading-tight;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus[data-flux-control],
|
|
||||||
textarea:focus[data-flux-control],
|
|
||||||
select:focus[data-flux-control] {
|
|
||||||
@apply outline-hidden ring-2 ring-accent ring-offset-2 ring-offset-accent-foreground;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Color Palette
|
|
||||||
|
|
||||||
### Primary Colors
|
|
||||||
- **Zinc Palette** (grays): 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950
|
|
||||||
- Used for backgrounds, borders, text
|
|
||||||
|
|
||||||
### Accent Colors
|
|
||||||
- **Dark Mode:** White/neutral-800 depending on context
|
|
||||||
- **Light Mode:** neutral-800
|
|
||||||
- **Accent Foreground:** White (light mode), neutral-800 (dark mode)
|
|
||||||
- **Accent Content:** neutral-800 (light), white (dark)
|
|
||||||
|
|
||||||
### Status Colors
|
|
||||||
- **Success:** green-500, green-600
|
|
||||||
- **Warning:** amber-500, amber-400
|
|
||||||
- **Error:** red-500, red-400, red-600
|
|
||||||
- **Info:** slate-500
|
|
||||||
|
|
||||||
### Semantic Colors (from Station Search)
|
|
||||||
- **Current Price (fresh):** green-500 (#22c55e)
|
|
||||||
- **Recent (24-48h):** slate-500 (#64748b)
|
|
||||||
- **Stale (2-5 days):** amber-500 (#f59e0b)
|
|
||||||
- **Outdated (5+ days):** red-500 (#ef4444)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Typography
|
|
||||||
|
|
||||||
### Font Family
|
|
||||||
- **Primary:** 'Instrument Sans' (Weights: 400, 500, 600)
|
|
||||||
- **Fallback:** ui-sans-serif, system-ui, sans-serif
|
|
||||||
- **Emoji:** Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji
|
|
||||||
|
|
||||||
### Font Sizes & Weights (via Flux)
|
|
||||||
- **Heading (XL):** Used for page titles
|
|
||||||
- **Heading (LG):** Used for section titles
|
|
||||||
- **Subheading:** Used for descriptions
|
|
||||||
- **Text:** Standard body text
|
|
||||||
- **Label:** Form labels
|
|
||||||
- **Badge:** Small text labels
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Spacing System
|
|
||||||
|
|
||||||
Based on Tailwind default scale:
|
|
||||||
- **Base Unit:** 4px (1 = 4px)
|
|
||||||
- **Common Spacing:**
|
|
||||||
- `gap-2`: 8px (0.5rem)
|
|
||||||
- `gap-3`: 12px (0.75rem)
|
|
||||||
- `gap-4`: 16px (1rem)
|
|
||||||
- `gap-6`: 24px (1.5rem)
|
|
||||||
- `px-4`: 16px horizontal padding
|
|
||||||
- `py-3`: 12px vertical padding
|
|
||||||
- `p-6`: 24px all sides padding
|
|
||||||
- `p-10`: 40px all sides padding
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Border & Radius
|
|
||||||
|
|
||||||
### Border Colors
|
|
||||||
- **Light:** `border-zinc-200`
|
|
||||||
- **Dark:** `border-zinc-700` (primary), `border-stone-800` (cards), `border-neutral-800` (split layout)
|
|
||||||
|
|
||||||
### Border Radius
|
|
||||||
- **Input/buttons:** Default Flux radius
|
|
||||||
- **Cards:** `rounded-xl` (larger radius)
|
|
||||||
- **Icons/small:** `rounded-md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dark Mode
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
- Uses `class="dark"` on `<html>` root
|
|
||||||
- Custom variant: `@custom-variant dark (&:where(.dark, .dark *))`
|
|
||||||
- Flux `@fluxAppearance` directive manages theme toggle
|
|
||||||
|
|
||||||
### Dark Mode Colors
|
|
||||||
- **Body BG:** `dark:bg-zinc-800`
|
|
||||||
- **Header/Sidebar BG:** `dark:bg-zinc-900`
|
|
||||||
- **Borders:** `dark:border-zinc-700`
|
|
||||||
- **Text:** `dark:text-zinc-100` (headings), `dark:text-zinc-400` (secondary)
|
|
||||||
|
|
||||||
### Accent in Dark Mode
|
|
||||||
- `--color-accent: var(--color-white)`
|
|
||||||
- `--color-accent-foreground: var(--color-neutral-800)`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Responsive Design
|
|
||||||
|
|
||||||
### Breakpoints (Tailwind defaults)
|
|
||||||
- **sm:** 640px
|
|
||||||
- **md:** 768px
|
|
||||||
- **lg:** 1024px
|
|
||||||
- **xl:** 1280px
|
|
||||||
- **2xl:** 1536px
|
|
||||||
|
|
||||||
### Usage Patterns
|
|
||||||
- `max-lg:hidden` - Hide on mobile/tablet
|
|
||||||
- `lg:hidden` - Hide on desktop
|
|
||||||
- `max-md:flex-col` - Stack on small screens
|
|
||||||
- `sm:flex-row` - Row layout on small+ screens
|
|
||||||
- `md:w-[220px]` - Fixed width on medium+
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Component-Specific Theming
|
|
||||||
|
|
||||||
### Forms (Flux)
|
|
||||||
- `[data-flux-field]`: grid gap-2 layout
|
|
||||||
- `[data-flux-label]`: No margin-bottom, tight line-height
|
|
||||||
- Focus states: ring-2 ring-accent with ring-offset
|
|
||||||
|
|
||||||
### Flux Appearance
|
|
||||||
- Automatically applied via `@fluxAppearance` directive
|
|
||||||
- Manages light/dark mode toggle
|
|
||||||
- Integrates with browser's prefers-color-scheme
|
|
||||||
|
|
||||||
### Leaflet Map
|
|
||||||
- Uses default OSM tiles
|
|
||||||
- Custom marker colors: green (current), slate (recent), amber (stale), red (outdated)
|
|
||||||
- Markers: 9px radius, white stroke, 2px weight, 85% fill opacity
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Brand Identity
|
|
||||||
|
|
||||||
### Logo
|
|
||||||
- **Primary Icon:** Custom SVG (geometric design)
|
|
||||||
- **Color:** Adapts to theme (black light mode, white dark mode)
|
|
||||||
- **Name:** "Laravel Starter Kit"
|
|
||||||
- **Contexts:**
|
|
||||||
- Header/nav: `flux:brand`
|
|
||||||
- Sidebar: `flux:sidebar.brand`
|
|
||||||
|
|
||||||
### Loading/Placeholder States
|
|
||||||
- Pattern SVG with diagonal lines
|
|
||||||
- Color: `stroke-gray-900/20` (light) or `stroke-neutral-100/20` (dark)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Animations & Transitions
|
|
||||||
|
|
||||||
### Alpine.js Transitions
|
|
||||||
- **Message Dismiss:** `x-show.transition.out.opacity.duration.1500ms`
|
|
||||||
- **Fade effects:** opacity transitions over 1500ms
|
|
||||||
|
|
||||||
### Flux Components
|
|
||||||
- Built-in animations for:
|
|
||||||
- Dropdown menus
|
|
||||||
- Sidebar collapse/expand
|
|
||||||
- Modal opens/closes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CSS Custom Properties (Tokens)
|
|
||||||
|
|
||||||
```css
|
|
||||||
--font-sans: 'Instrument Sans', ...
|
|
||||||
--color-zinc-50 through --color-zinc-950
|
|
||||||
--color-accent
|
|
||||||
--color-accent-content
|
|
||||||
--color-accent-foreground
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vite Build Configuration
|
|
||||||
|
|
||||||
**File:** `vite.config.js`
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import {
|
|
||||||
defineConfig
|
|
||||||
} from 'vite';
|
|
||||||
import laravel from 'laravel-vite-plugin';
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
laravel({
|
|
||||||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
|
||||||
refresh: true,
|
|
||||||
}),
|
|
||||||
tailwindcss(),
|
|
||||||
],
|
|
||||||
server: {
|
|
||||||
cors: true,
|
|
||||||
watch: {
|
|
||||||
ignored: ['**/storage/framework/views/**'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Tailwind CSS v4 via @tailwindcss/vite plugin
|
|
||||||
- Automatic CSS/JS refresh
|
|
||||||
- CORS enabled for dev server
|
|
||||||
- Ignores Laravel view cache files
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Flux UI Theme Configuration
|
|
||||||
|
|
||||||
Flux v2 is configured via:
|
|
||||||
- CSS import: `@import '../../vendor/livewire/flux/dist/flux.css'`
|
|
||||||
- Content sources point to Flux stubs for autocomplete
|
|
||||||
- `@fluxAppearance` handles dark mode toggling
|
|
||||||
- `@fluxScripts` loads JavaScript enhancements
|
|
||||||
|
|
||||||
No custom Flux config file exists; uses defaults with CSS customizations.
|
|
||||||
|
|
||||||
@@ -129,8 +129,9 @@ final class HandleStripeWebhook
|
|||||||
|
|
||||||
private function bustPlanCache(User $user): void
|
private function bustPlanCache(User $user): void
|
||||||
{
|
{
|
||||||
$tag = Cache::tags(['plans']);
|
$cache = Cache::supportsTags() ? Cache::tags(['plans']) : Cache::store();
|
||||||
$tag->forget("plan_for_user_{$user->id}");
|
|
||||||
$tag->forget("plan_cadence_for_user_{$user->id}");
|
$cache->forget("plan_for_user_{$user->id}");
|
||||||
|
$cache->forget("plan_cadence_for_user_{$user->id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
415
docs/ops/deployment.md
Normal file
415
docs/ops/deployment.md
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
# VPS Deployment Runbook — FuelAlert
|
||||||
|
|
||||||
|
How to deploy and run this app on the IONOS VPS (Nginx + PHP-FPM + MySQL + Redis).
|
||||||
|
|
||||||
|
Two parts:
|
||||||
|
- **First-time setup** (§1–§7) — done once when provisioning the server.
|
||||||
|
- **Every deploy** (§8) — the short sequence you repeat each time you ship.
|
||||||
|
|
||||||
|
If something breaks after deploy, jump to **§10 Troubleshooting** — most live
|
||||||
|
problems are one of four things.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Server prerequisites
|
||||||
|
|
||||||
|
Install these once on the VPS:
|
||||||
|
|
||||||
|
| Software | Why | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| **PHP 8.4** + FPM | runs the app | extensions: `mbstring, pdo_mysql, redis, intl, bcmath, curl, xml, zip, gd` |
|
||||||
|
| **Composer 2** | PHP deps | |
|
||||||
|
| **Node 22 + npm** | builds the Vue SPA assets | only needed to run `npm run build` |
|
||||||
|
| **MySQL 8** | database | InnoDB |
|
||||||
|
| **Redis** | queue + cache | |
|
||||||
|
| **Nginx** | web server | serves `public/` |
|
||||||
|
| **Git** | pulls the code | |
|
||||||
|
| **Certbot** | HTTPS cert | Sanctum cookie auth requires HTTPS |
|
||||||
|
| **Supervisor** *or* systemd | keeps the queue worker alive | systemd shown below |
|
||||||
|
|
||||||
|
Quick check after install: `php -v`, `composer -V`, `node -v`, `redis-cli ping` (→ `PONG`), `mysql --version`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Get the code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /var/www
|
||||||
|
git clone <your-gitea-repo-url> fuel-alert
|
||||||
|
cd fuel-alert
|
||||||
|
git checkout main # main = live (see §9 for tagging releases)
|
||||||
|
```
|
||||||
|
|
||||||
|
The app lives at `/var/www/fuel-alert`. Adjust paths below if you use another location.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Create the production `.env`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
php artisan key:generate # sets APP_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
Then edit `.env`. **The values below are the ones that matter for production** —
|
||||||
|
see §11 for the full reference table.
|
||||||
|
|
||||||
|
### Critical — app
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
APP_NAME=FuelAlert
|
||||||
|
APP_ENV=production
|
||||||
|
APP_DEBUG=false # NEVER true on live — leaks stack traces + secrets
|
||||||
|
APP_URL=https://fuel-alert.co.uk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Critical — SPA cookie/session auth (the #1 "login broke on live" trap)
|
||||||
|
|
||||||
|
This app is a Vue SPA using Sanctum cookie auth. If these don't match your real
|
||||||
|
domain over HTTPS, login/registration fail with 419/401 even though the rest of
|
||||||
|
the site looks fine:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
SESSION_DOMAIN=.fuel-alert.co.uk
|
||||||
|
SESSION_SECURE_COOKIE=true
|
||||||
|
SANCTUM_STATEFUL_DOMAINS=fuel-alert.co.uk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Critical — database / redis / queue / cache
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=fuel_alert
|
||||||
|
DB_USERNAME=fuel_alert
|
||||||
|
DB_PASSWORD=<strong-password>
|
||||||
|
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
|
||||||
|
QUEUE_CONNECTION=redis # a worker MUST run — see §6
|
||||||
|
CACHE_STORE=redis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Critical — your own API gate
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
API_SECRET_KEY=<long-random-string>
|
||||||
|
```
|
||||||
|
|
||||||
|
`API_SECRET_KEY` gates the station-search API (`VerifyApiKey` middleware). The SPA
|
||||||
|
sends the matching key. If it's missing/wrong, `GET /api/stations` returns 401 and
|
||||||
|
the search page shows nothing.
|
||||||
|
|
||||||
|
### Mail (Ionos SMTP)
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=smtp.ionos.co.uk
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_SCHEME=tls
|
||||||
|
MAIL_USERNAME=<ionos-mailbox>
|
||||||
|
MAIL_PASSWORD=<ionos-password>
|
||||||
|
MAIL_FROM_ADDRESS=hello@fuel-alert.co.uk
|
||||||
|
MAIL_FROM_NAME=FuelAlert
|
||||||
|
```
|
||||||
|
|
||||||
|
### External data APIs (the product needs these to have live data)
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
FUEL_FINDER_CLIENT_ID=
|
||||||
|
FUEL_FINDER_CLIENT_SECRET=
|
||||||
|
FUEL_FINDER_BASE_URL=https://www.fuel-finder.service.gov.uk/api/v1
|
||||||
|
FRED_API_KEY=
|
||||||
|
ANTHROPIC_API_KEY=
|
||||||
|
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
|
||||||
|
EIA_API_KEY=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notification providers (fill when those channels go live)
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
ONESIGNAL_APP_ID=
|
||||||
|
ONESIGNAL_API_KEY=
|
||||||
|
VONAGE_KEY=
|
||||||
|
VONAGE_SECRET=
|
||||||
|
VONAGE_WHATSAPP_FROM=
|
||||||
|
VONAGE_SMS_FROM=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stripe — can be deferred
|
||||||
|
|
||||||
|
If launching free-only first, leave Stripe **test** keys and don't promote paid
|
||||||
|
plans. When you go live with payments, see §7.
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
CASHIER_CURRENCY=gbp
|
||||||
|
STRIPE_KEY=
|
||||||
|
STRIPE_SECRET=
|
||||||
|
STRIPE_WEBHOOK_SECRET=
|
||||||
|
STRIPE_PRICE_BASIC_MONTHLY=
|
||||||
|
STRIPE_PRICE_BASIC_ANNUAL=
|
||||||
|
STRIPE_PRICE_PLUS_MONTHLY=
|
||||||
|
STRIPE_PRICE_PLUS_ANNUAL=
|
||||||
|
STRIPE_PRICE_PRO_MONTHLY=
|
||||||
|
STRIPE_PRICE_PRO_ANNUAL=
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Remember:** after ANY `.env` change on a cached production box, re-run
|
||||||
|
> `php artisan config:cache` or the change won't take effect (see §8).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Install dependencies & build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
npm ci && npm run build # compiles the Vue SPA into public/build
|
||||||
|
```
|
||||||
|
|
||||||
|
`--no-dev` skips dev-only packages. `npm run build` is required — without it the
|
||||||
|
SPA has no compiled assets and you get a blank page / Vite manifest error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Database: migrate + seed plans
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan migrate --force # --force = run in production non-interactively
|
||||||
|
php artisan db:seed --class=PlanSeeder --force # REQUIRED
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Do not** run `migrate:fresh`, `migrate:reset`, or `db:wipe` on the server —
|
||||||
|
> they destroy data. Only `migrate` (forward) is safe.
|
||||||
|
|
||||||
|
`PlanSeeder` populates the `plans` table. The entire tier/entitlement system
|
||||||
|
(`PlanFeatures`) resolves through these rows — skip it and features misbehave for
|
||||||
|
every user. It's idempotent, so it's safe to re-run.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Storage link + production caches
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan storage:link
|
||||||
|
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan event:cache
|
||||||
|
```
|
||||||
|
|
||||||
|
The cache commands make production fast. Trade-off: cached config ignores later
|
||||||
|
`.env` edits until you re-run `config:cache`.
|
||||||
|
|
||||||
|
### File permissions
|
||||||
|
|
||||||
|
The web user (usually `www-data`) must be able to write to two dirs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo chown -R www-data:www-data storage bootstrap/cache
|
||||||
|
sudo find storage -type d -exec chmod 775 {} \;
|
||||||
|
sudo find storage -type f -exec chmod 664 {} \;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Background processes (the part everyone forgets)
|
||||||
|
|
||||||
|
The app is not just web requests. Two things must run continuously or the product
|
||||||
|
silently stops working.
|
||||||
|
|
||||||
|
### 6a. Scheduler (cron) — keeps prices & predictions fresh
|
||||||
|
|
||||||
|
The app schedules the entire data pipeline: `fuel:poll`, `oil:fetch`,
|
||||||
|
`forecast:llm-overlay`, `beis:import`, `forecast:resolve-outcomes`,
|
||||||
|
`forecast:evaluate-volatility`, `fuel:archive`, plus morning/evening WhatsApp jobs.
|
||||||
|
Without cron, live data goes stale.
|
||||||
|
|
||||||
|
Add ONE cron entry (`crontab -e` as the app user):
|
||||||
|
|
||||||
|
```cron
|
||||||
|
* * * * * cd /var/www/fuel-alert && php artisan schedule:run >> /dev/null 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Laravel's scheduler decides internally which task runs when — you only need this
|
||||||
|
one line.
|
||||||
|
|
||||||
|
### 6b. Queue worker — sends notifications, processes polling jobs
|
||||||
|
|
||||||
|
Notifications and polling run as queued jobs. No worker = nothing ever sends.
|
||||||
|
Run it as a systemd service so it restarts on crash/reboot.
|
||||||
|
|
||||||
|
Create `/etc/systemd/system/fuelalert-worker.service`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=FuelAlert queue worker
|
||||||
|
After=network.target redis.service mysql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=www-data
|
||||||
|
Group=www-data
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
WorkingDirectory=/var/www/fuel-alert
|
||||||
|
ExecStart=/usr/bin/php artisan queue:work redis --queue=notifications,default --tries=3 --max-time=3600
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now fuelalert-worker
|
||||||
|
sudo systemctl status fuelalert-worker # should be "active (running)"
|
||||||
|
```
|
||||||
|
|
||||||
|
> The `notifications` queue is listed first so alerts get priority over default jobs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Nginx + HTTPS
|
||||||
|
|
||||||
|
Server block (`/etc/nginx/sites-available/fuel-alert`):
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name fuel-alert.co.uk www.fuel-alert.co.uk;
|
||||||
|
root /var/www/fuel-alert/public;
|
||||||
|
|
||||||
|
index index.php;
|
||||||
|
charset utf-8;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.(?!well-known).* { deny all; }
|
||||||
|
client_max_body_size 20M;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/fuel-alert /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
sudo certbot --nginx -d fuel-alert.co.uk -d www.fuel-alert.co.uk # HTTPS — required for secure cookies
|
||||||
|
```
|
||||||
|
|
||||||
|
`root` points at `public/`, never the project root. The SPA routing is handled by
|
||||||
|
Laravel's catch-all in `routes/web.php` via `index.php`, so the standard
|
||||||
|
`try_files … /index.php` block is all you need.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Every deploy (the repeatable sequence)
|
||||||
|
|
||||||
|
After the first-time setup, each deploy is just this. Save it as
|
||||||
|
`deploy.sh` in the project root (`chmod +x deploy.sh`) and run `./deploy.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd /var/www/fuel-alert
|
||||||
|
|
||||||
|
php artisan down --render="errors::503" # maintenance mode (optional)
|
||||||
|
|
||||||
|
git fetch --tags
|
||||||
|
git checkout "${1:-main}" # ./deploy.sh v0.2.0 → deploy a tag; no arg → main
|
||||||
|
git pull --ff-only || true
|
||||||
|
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
npm ci && npm run build
|
||||||
|
|
||||||
|
php artisan migrate --force
|
||||||
|
|
||||||
|
# refresh caches (config:cache picks up any .env changes)
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan event:cache
|
||||||
|
|
||||||
|
php artisan queue:restart # workers reload the NEW code
|
||||||
|
php artisan up
|
||||||
|
|
||||||
|
php artisan about # sanity check: env=production, debug=false
|
||||||
|
```
|
||||||
|
|
||||||
|
> `queue:restart` is important: long-running workers keep the OLD code in memory
|
||||||
|
> until told to restart. Skip it and your new code won't run in queued jobs.
|
||||||
|
>
|
||||||
|
> Only run `db:seed --class=PlanSeeder --force` again if you changed plan/feature
|
||||||
|
> definitions — it's safe (idempotent) but usually unnecessary per deploy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Tagging a release (rollback points)
|
||||||
|
|
||||||
|
`main` is live. Tag the commit you actually deploy so you have a named, verified
|
||||||
|
rollback point:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# locally, once the deploy is confirmed working:
|
||||||
|
git tag -a v0.1.0 -m "first live version"
|
||||||
|
git push origin v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Roll back by deploying an older tag: `./deploy.sh v0.1.0`. List tags: `git tag`.
|
||||||
|
Bump the middle number for meaningful releases, the last for small fixes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Troubleshooting — the four usual suspects
|
||||||
|
|
||||||
|
| Symptom | Likely cause | Fix |
|
||||||
|
|---|---|---|
|
||||||
|
| Login/register fails with **419** or **401** | SPA cookie domains wrong | Check `APP_URL`, `SESSION_DOMAIN`, `SANCTUM_STATEFUL_DOMAINS`, `SESSION_SECURE_COOKIE` in `.env`, then `config:cache` |
|
||||||
|
| Station search returns **401 / empty** | `API_SECRET_KEY` missing or mismatched | Set it in `.env`, `config:cache`, rebuild SPA if the key is baked into the build |
|
||||||
|
| Prices/predictions are **stale or empty** | scheduler cron not running | Verify the `* * * * *` cron line; test with `php artisan schedule:run` manually |
|
||||||
|
| Notifications **never arrive** | queue worker not running | `systemctl status fuelalert-worker`; check `storage/logs/laravel.log` |
|
||||||
|
| `.env` change **had no effect** | config is cached | `php artisan config:cache` |
|
||||||
|
| **Blank page** / "Vite manifest not found" | assets not built | `npm ci && npm run build` |
|
||||||
|
| **500** right after deploy | permissions on storage | re-run the `chown`/`chmod` in §5; check `storage/logs/laravel.log` |
|
||||||
|
|
||||||
|
Useful commands: `php artisan about` (env summary), `tail -f storage/logs/laravel.log`
|
||||||
|
(live errors), `redis-cli ping`, `sudo systemctl status fuelalert-worker`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Environment variable reference
|
||||||
|
|
||||||
|
Keys that need real production values (from `.env.example`):
|
||||||
|
|
||||||
|
**App:** `APP_NAME` `APP_ENV=production` `APP_KEY` `APP_DEBUG=false` `APP_URL`
|
||||||
|
**Session/SPA:** `SESSION_DRIVER` `SESSION_DOMAIN` `SESSION_SECURE_COOKIE` `SANCTUM_STATEFUL_DOMAINS`
|
||||||
|
**Database:** `DB_CONNECTION` `DB_HOST` `DB_PORT` `DB_DATABASE` `DB_USERNAME` `DB_PASSWORD`
|
||||||
|
**Redis/queue/cache:** `REDIS_CLIENT` `REDIS_HOST` `REDIS_PORT` `REDIS_PASSWORD` `QUEUE_CONNECTION` `CACHE_STORE`
|
||||||
|
**Your API gate:** `API_SECRET_KEY`
|
||||||
|
**Mail (Ionos):** `MAIL_MAILER` `MAIL_HOST` `MAIL_PORT` `MAIL_SCHEME` `MAIL_USERNAME` `MAIL_PASSWORD` `MAIL_FROM_ADDRESS` `MAIL_FROM_NAME`
|
||||||
|
**Fuel data:** `FUEL_FINDER_CLIENT_ID` `FUEL_FINDER_CLIENT_SECRET` `FUEL_FINDER_BASE_URL` `FRED_API_KEY` `EIA_API_KEY`
|
||||||
|
**LLM:** `ANTHROPIC_API_KEY` `ANTHROPIC_MODEL` `LLM_PREDICTION_PROVIDER`
|
||||||
|
**Notifications:** `ONESIGNAL_APP_ID` `ONESIGNAL_API_KEY` `VONAGE_KEY` `VONAGE_SECRET` `VONAGE_WHATSAPP_FROM` `VONAGE_SMS_FROM`
|
||||||
|
**Stripe (deferrable):** `STRIPE_KEY` `STRIPE_SECRET` `STRIPE_WEBHOOK_SECRET` `CASHIER_CURRENCY` `STRIPE_PRICE_*`
|
||||||
|
|
||||||
|
### Stripe go-live (when payments launch)
|
||||||
|
|
||||||
|
1. Swap in live `STRIPE_KEY` / `STRIPE_SECRET` and all six `STRIPE_PRICE_*` IDs.
|
||||||
|
2. In the Stripe dashboard, add a webhook endpoint:
|
||||||
|
`https://fuel-alert.co.uk/stripe/webhook`
|
||||||
|
3. Copy its signing secret into `STRIPE_WEBHOOK_SECRET`.
|
||||||
|
4. `php artisan config:cache`.
|
||||||
|
5. Configure Stripe dashboard retries (days 1/3/5, cancel after final) for the
|
||||||
|
grace-period dunning flow.
|
||||||
@@ -24,6 +24,24 @@ it('busts the plan cache on customer.subscription.created', function (): void {
|
|||||||
expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull();
|
expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('busts the plan cache without error on a cache store that does not support tags', function (): void {
|
||||||
|
// The `file` driver is not taggable — calling Cache::tags() on it throws.
|
||||||
|
// This guards against a regression where bustPlanCache assumed a taggable store.
|
||||||
|
config(['cache.default' => 'file']);
|
||||||
|
Cache::store('file')->flush();
|
||||||
|
expect(Cache::supportsTags())->toBeFalse();
|
||||||
|
|
||||||
|
$user = User::factory()->create(['stripe_id' => 'cus_notags_1']);
|
||||||
|
Cache::put("plan_for_user_{$user->id}", 'stale', 3600);
|
||||||
|
|
||||||
|
(new HandleStripeWebhook)->handle(new WebhookReceived([
|
||||||
|
'type' => 'customer.subscription.created',
|
||||||
|
'data' => ['object' => ['customer' => 'cus_notags_1']],
|
||||||
|
]));
|
||||||
|
|
||||||
|
expect(Cache::get("plan_for_user_{$user->id}"))->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('ignores subscription.created when the user is not found', function (): void {
|
it('ignores subscription.created when the user is not found', function (): void {
|
||||||
(new HandleStripeWebhook)->handle(new WebhookReceived([
|
(new HandleStripeWebhook)->handle(new WebhookReceived([
|
||||||
'type' => 'customer.subscription.created',
|
'type' => 'customer.subscription.created',
|
||||||
|
|||||||
Reference in New Issue
Block a user