diff --git a/.claude/tdd-guard/data/test.json b/.claude/tdd-guard/data/test.json index ee238d4..a1202ea 100644 --- a/.claude/tdd-guard/data/test.json +++ b/.claude/tdd-guard/data/test.json @@ -1,7 +1,7 @@ { "testModules": [ { - "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/hooks/__tests__/useDashboardData.test.ts", + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/pages/__tests__/GuidePage.test.tsx", "tests": [ { "name": "Module failed to load (Error)", @@ -11,58 +11,7 @@ { "message": "File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)", "name": "Error", - "stack": "Error: File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)\n at ConfigSet.resolvePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:616:19)\n at ConfigSet._setupConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:322:71)\n at new ConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:206:14)\n at TsJestTransformer._createConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:119:16)\n at TsJestTransformer._configsFor (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:98:34)\n at TsJestTransformer.getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:249:30)\n at ScriptTransformer._getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:195:41)\n at ScriptTransformer._getFileCachePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:231:27)\n at ScriptTransformer.transformSource (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:402:32)\n at ScriptTransformer._transformAndBuildScript (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:519:40)\n at ScriptTransformer.transform (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:558:19)\n at Runtime.transformFile (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1290:53)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1243:34)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:944:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:832:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-circus/build/runner.js:84:33)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:275:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:343:7)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:497:12)" - } - ] - } - ] - }, - { - "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/DashboardScreen.test.tsx", - "tests": [ - { - "name": "Module failed to load (Error)", - "fullName": "Module failed to load (Error)", - "state": "failed", - "errors": [ - { - "message": "File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)", - "name": "Error", - "stack": "Error: File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)\n at ConfigSet.resolvePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:616:19)\n at ConfigSet._setupConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:322:71)\n at new ConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:206:14)\n at TsJestTransformer._createConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:119:16)\n at TsJestTransformer._configsFor (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:98:34)\n at TsJestTransformer.getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:249:30)\n at ScriptTransformer._getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:195:41)\n at ScriptTransformer._getFileCachePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:231:27)\n at ScriptTransformer.transformSource (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:402:32)\n at ScriptTransformer._transformAndBuildScript (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:519:40)\n at ScriptTransformer.transform (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:558:19)\n at Runtime.transformFile (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1290:53)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1243:34)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:944:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:832:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-circus/build/runner.js:84:33)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:275:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:343:7)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:497:12)" - } - ] - } - ] - }, - { - "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/ActionBar.test.tsx", - "tests": [ - { - "name": "Module failed to load (Error)", - "fullName": "Module failed to load (Error)", - "state": "failed", - "errors": [ - { - "message": "File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)", - "name": "Error", - "stack": "Error: File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)\n at ConfigSet.resolvePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:616:19)\n at ConfigSet._setupConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:322:71)\n at new ConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:206:14)\n at TsJestTransformer._createConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:119:16)\n at TsJestTransformer._configsFor (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:98:34)\n at TsJestTransformer.getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:249:30)\n at ScriptTransformer._getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:195:41)\n at ScriptTransformer._getFileCachePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:231:27)\n at ScriptTransformer.transformSource (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:402:32)\n at ScriptTransformer._transformAndBuildScript (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:519:40)\n at ScriptTransformer.transform (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:558:19)\n at Runtime.transformFile (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1290:53)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1243:34)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:944:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:832:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-circus/build/runner.js:84:33)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:275:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:343:7)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:497:12)" - } - ] - } - ] - }, - { - "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/VehicleRosterCard.test.tsx", - "tests": [ - { - "name": "Module failed to load (Error)", - "fullName": "Module failed to load (Error)", - "state": "failed", - "errors": [ - { - "message": "File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)", - "name": "Error", - "stack": "Error: File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)\n at ConfigSet.resolvePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:616:19)\n at ConfigSet._setupConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:322:71)\n at new ConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:206:14)\n at TsJestTransformer._createConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:119:16)\n at TsJestTransformer._configsFor (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:98:34)\n at TsJestTransformer.getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:249:30)\n at ScriptTransformer._getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:195:41)\n at ScriptTransformer._getFileCachePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:231:27)\n at ScriptTransformer.transformSource (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:402:32)\n at ScriptTransformer._transformAndBuildScript (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:519:40)\n at ScriptTransformer.transform (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:558:19)\n at Runtime.transformFile (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1290:53)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1243:34)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:944:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:832:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-circus/build/runner.js:84:33)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:275:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:343:7)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/testWorker.js:497:12)" + "stack": "Error: File not found: tsconfig.json (resolved as: /Users/egullickson/Documents/Technology/coding/motovaultpro/tsconfig.json)\n at ConfigSet.resolvePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:616:19)\n at ConfigSet._setupConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:322:71)\n at new ConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/config/config-set.js:206:14)\n at TsJestTransformer._createConfigSet (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:119:16)\n at TsJestTransformer._configsFor (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:98:34)\n at TsJestTransformer.getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/ts-jest/dist/legacy/ts-jest-transformer.js:249:30)\n at ScriptTransformer._getCacheKey (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:195:41)\n at ScriptTransformer._getFileCachePath (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:231:27)\n at ScriptTransformer.transformSource (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:402:32)\n at ScriptTransformer._transformAndBuildScript (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:519:40)\n at ScriptTransformer.transform (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/@jest/transform/build/index.js:558:19)\n at Runtime.transformFile (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1290:53)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:1243:34)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:944:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runtime/build/index.js:832:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-circus/build/runner.js:84:33)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/index.js:275:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/node_modules/jest-runner/build/index.js:343:7)" } ] } diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md new file mode 100644 index 0000000..50163f5 --- /dev/null +++ b/docs/USER-GUIDE.md @@ -0,0 +1,955 @@ +# MotoVaultPro User Guide + +Precision Vehicle Management -- Track every mile. Own every detail. + +MotoVaultPro is a cloud-based vehicle management platform for car enthusiasts and collectors. It tracks your entire fleet in one place: maintenance histories, fuel logs, documents, gas stations, and performance analytics. + +This guide walks through every feature of the application. + +--- + +## Table of Contents + +1. [Getting Started](#1-getting-started) + - [Creating an Account](#creating-an-account) + - [Logging In](#logging-in) + - [Onboarding](#onboarding) + - [Trouble Logging In](#trouble-logging-in) +2. [Dashboard](#2-dashboard) + - [Your Fleet Overview](#your-fleet-overview) + - [Quick Actions](#quick-actions) +3. [Vehicles](#3-vehicles) + - [Viewing Your Vehicles](#viewing-your-vehicles) + - [Adding a Vehicle](#adding-a-vehicle) + - [VIN Decode](#vin-decode) + - [Vehicle Detail Page](#vehicle-detail-page) + - [Editing a Vehicle](#editing-a-vehicle) + - [Deleting a Vehicle](#deleting-a-vehicle) +4. [Fuel Logs](#4-fuel-logs) + - [Fuel Logs Overview](#fuel-logs-overview) + - [Logging Fuel](#logging-fuel) + - [Receipt Scanning](#receipt-scanning) + - [Editing and Deleting Fuel Logs](#editing-and-deleting-fuel-logs) +5. [Maintenance](#5-maintenance) + - [Maintenance Records](#maintenance-records) + - [Adding a Maintenance Record](#adding-a-maintenance-record) + - [Maintenance Schedules](#maintenance-schedules) + - [Creating a Schedule](#creating-a-schedule) +6. [Gas Stations](#6-gas-stations) + - [Finding Stations](#finding-stations) + - [Saved Stations](#saved-stations) + - [Premium 93 Stations](#premium-93-stations) +7. [Documents](#7-documents) + - [Documents Overview](#documents-overview) + - [Adding a Document](#adding-a-document) + - [Document Types](#document-types) +8. [Settings](#8-settings) + - [Profile](#profile) + - [Security and Privacy](#security-and-privacy) + - [Subscription](#subscription) + - [Notifications](#notifications) + - [Appearance and Units](#appearance-and-units) + - [Data Import and Export](#data-import-and-export) + - [Account Actions](#account-actions) +9. [Subscription Tiers and Pro Features](#9-subscription-tiers-and-pro-features) + - [Tier Comparison](#tier-comparison) + - [VIN Camera Scanning and Decode (Pro)](#vin-camera-scanning-and-decode-pro) + - [Fuel Receipt Scanning (Pro)](#fuel-receipt-scanning-pro) + - [Maintenance Receipt Scanning (Pro)](#maintenance-receipt-scanning-pro) + - [Maintenance Manual PDF Extraction (Pro)](#maintenance-manual-pdf-extraction-pro) + - [Email Ingestion (Pro)](#email-ingestion-pro) + - [Shared Vehicle Documents (Pro)](#shared-vehicle-documents-pro) + - [Community Station Submissions (Pro)](#community-station-submissions-pro) + - [Managing Your Subscription](#managing-your-subscription) +10. [Mobile Experience](#10-mobile-experience) + +--- + +## 1. Getting Started + +### Creating an Account + +Navigate to [motovaultpro.com](https://motovaultpro.com) and click the **Sign Up** button in the top-right corner of the navigation bar. + +**Sign Up Page** + +The registration page displays the MotoVaultPro logo and a clean form with the following fields: + +| Field | Required | Details | +|-------|----------|---------| +| Email Address | Yes | Your email address (e.g., your.email@example.com) | +| Password | Yes | Minimum 8 characters, must include one uppercase letter and one number | +| Confirm Password | Yes | Re-enter your password to confirm | +| Terms & Conditions | Yes | Checkbox -- you must agree to the Terms & Conditions before creating your account | + +After filling in all fields, click the **Create Account** button. + +If you already have an account, click the **Login** link at the bottom of the form. + +After registration, you will receive a verification email. Click the link in the email to verify your account before logging in. + +### Logging In + +Click the **Login** button in the top-right corner of the navigation bar. You will be redirected to the secure login page powered by Auth0. + +**Login Page** + +Enter your registered email address, then click **Continue**. On the next screen, enter your password and click **Continue** to log in. + +After successful authentication, you will be redirected to the Dashboard. + +### Onboarding + +First-time users see an onboarding flow with three steps: + +1. **Preferences** -- Choose your preferred unit system (Imperial or Metric), distance units, and notification preferences. +2. **Add Your First Vehicle** -- Enter your first vehicle's details (you can skip this step and add vehicles later). +3. **Complete** -- A welcome screen with quick links to get started exploring the app. + +### Trouble Logging In + +If you are having trouble logging in, try the following password reset and account recovery options. + +--- + +## 2. Dashboard + +After logging in, you land on the Dashboard -- your fleet headquarters. + +**What You See** + +The Dashboard displays a "Your Fleet" heading with all your vehicles shown as cards in a grid layout. Each vehicle card shows: + +- **Vehicle icon** -- A small colored indicator badge (varies by vehicle) +- **Vehicle name** -- The nickname or full name (e.g., "Beast", "MERLOT") +- **Health status** -- A green dot indicates "All clear" (no overdue maintenance); other colors indicate attention needed +- **Status text** -- "All clear" or a maintenance alert message +- **Odometer reading** -- Current mileage (e.g., "35,000 mi") + +**Click any vehicle card** to go directly to that vehicle's detail page. + +### Quick Actions + +Two action buttons appear in the top-right corner of the Dashboard: + +| Button | Action | +|--------|--------| +| **+ Add Vehicle** | Opens the Add Vehicle form on the Vehicles page | +| **LOG FUEL** | Opens the fuel logging modal to quickly record a fill-up | + +These quick actions let you perform the most common tasks without navigating away from the Dashboard. + +### Navigation Sidebar + +The left sidebar provides access to all sections of the app: + +| Menu Item | Description | +|-----------|-------------| +| **Dashboard** | Fleet overview (home) | +| **Vehicles** | Manage your vehicle collection | +| **Fuel Logs** | Track fuel purchases and efficiency | +| **Maintenance** | Record service history and set schedules | +| **Gas Stations** | Find and save gas stations | +| **Documents** | Store vehicle-related documents | +| **Settings** | Account, preferences, and data management | + +At the bottom of the sidebar you will see your email address and a **Sign Out** button. + +The header bar at the top shows a notification bell icon and a "Welcome back" greeting with your email. + +--- + +## 3. Vehicles + +### Viewing Your Vehicles + +Click **Vehicles** in the sidebar to see the "My Vehicles" page. This page shows: + +- **Search bar** -- Search vehicles by name, make, model, or VIN +- **+ Add Vehicle** button -- Top-right corner +- **Vehicle cards** in a grid layout (3 columns on desktop), each displaying: + - Manufacturer logo (e.g., Chevrolet bowtie, GMC logo) + - Vehicle nickname or full name + - Year, Make, and Model + - License plate number + - Current odometer reading (in miles or kilometers) + - **Edit** (pencil icon) and **Delete** (trash icon) buttons at the bottom of each card + +### Adding a Vehicle + +Click the **+ Add Vehicle** button to expand the "Add New Vehicle" form directly on the Vehicles page. The form has the following sections: + +**Vehicle Photo** + +Upload a photo of your vehicle. Click **ADD PHOTO** to select an image file. +- Accepted formats: JPEG or PNG +- Maximum file size: 5MB + +**VIN Number** + +Enter your vehicle's 17-character VIN (Vehicle Identification Number). +- Type the VIN manually in the text field, OR +- Click the **camera icon** to scan the VIN using your device camera (uses OCR technology) +- Click the **Decode VIN** button to automatically populate vehicle details from the VIN + +Note: VIN is optional if you provide a License Plate instead. + +**Vehicle Specifications** + +These fields use cascading dropdowns -- each selection narrows the options for the next field: + +| Field | How It Works | +|-------|-------------| +| Year | Select the model year from the dropdown | +| Make | Available after selecting Year (e.g., Chevrolet, GMC, Ford) | +| Model | Available after selecting Make (e.g., Silverado, Camaro, Sierra) | +| Trim | Available after selecting Model (e.g., LT Double Cab 4WD) | +| Engine | Available after selecting Trim (e.g., 6.6L 401 HP V8) | +| Transmission | Available after selecting Trim (e.g., 10-Speed Automatic) | + +**Additional Details** + +| Field | Example | Notes | +|-------|---------|-------| +| Nickname | Beast, Family Car | A friendly name for your vehicle | +| Color | Black, Blue, Red | Vehicle color | +| License Plate | ABC-123 | Required if VIN is not provided | +| Current Odometer Reading | 50000 | Current mileage in your selected unit | + +**Purchase Information** + +| Field | Example | Notes | +|-------|---------|-------| +| Purchase Price | 25000 | What you paid for the vehicle | +| Purchase Date | mm/dd/yyyy | When you purchased the vehicle | + +Click **Add Vehicle** to save, or **Cancel** to discard. + +### VIN Decode + +> **Pro Feature:** VIN camera scanning and automatic decode require a Pro or Enterprise subscription. Free tier users can still type a VIN manually. See [VIN Camera Scanning and Decode (Pro)](#vin-camera-scanning-and-decode-pro) for full details. + +The VIN Decode feature automatically fills in vehicle details from a VIN: + +1. Enter or scan your 17-character VIN +2. Click the **Decode VIN** button +3. The system looks up the VIN and auto-populates: Year, Make, Model, Engine, Transmission, and Trim +4. Review the pre-filled fields and make any corrections +5. Continue filling in the remaining fields (Nickname, Color, etc.) + +### Vehicle Detail Page + +Click any vehicle card (from Dashboard or Vehicles list) to open the Vehicle Detail Page. This page shows everything about a single vehicle: + +**Header Area** +- Back arrow and **BACK** link to return to the previous page +- Vehicle nickname as the page title (e.g., "Beast") +- **Edit Vehicle** button (top-right) +- Quick action buttons: **Add Fuel Log** and **Add Maintenance** + +**Vehicle Details Section** +- Manufacturer logo +- Full vehicle description (e.g., "2022 Chevrolet Silverado 2500HD") +- VIN Number +- Year, Make, and Model (displayed in a 3-column row) +- Trim, Engine, and Transmission (displayed in a 3-column row) +- Nickname +- Color and License Plate (side by side) +- Current Odometer Reading + +**Purchase Information Section** +- Purchase Price +- Purchase Date + +**Ownership Costs Section** +- Tracks insurance, registration, taxes, and other recurring vehicle costs +- Shows "No ownership costs recorded yet" until costs are added + +**Vehicle Records Section** +- A table showing all records associated with this vehicle (fuel logs, maintenance records) +- Columns: Date, Type, Summary, Amount, Actions +- **Filter** dropdown to filter by record type (All, Fuel, Maintenance) + +### Editing a Vehicle + +From the Vehicle Detail Page, click the **Edit Vehicle** button. This opens the vehicle form pre-filled with the current values. Make your changes and save. + +From the Vehicles list, click the **pencil icon** on any vehicle card to edit it directly. + +### Deleting a Vehicle + +From the Vehicles list, click the **trash icon** on any vehicle card. You will be asked to confirm the deletion. Deleting a vehicle is permanent and removes all associated records. + +--- + +## 4. Fuel Logs + +### Fuel Logs Overview + +Click **Fuel Logs** in the sidebar to see the Fuel Logs page. At the top, you see summary statistics: + +| Metric | Description | +|--------|-------------| +| **LOGS** | Total number of fuel entries | +| **TOTAL FUEL** | Total gallons (or liters) across all fill-ups | +| **TOTAL COST** | Total amount spent on fuel | + +Below the summary, a table lists all your fuel log entries. If you have no entries yet, you will see "No fuel logs yet." + +The **+ Add Fuel Log** button is in the top-right corner. + +### Logging Fuel + +Click **+ Add Fuel Log** (or the **LOG FUEL** button from the Dashboard) to open the "Log Fuel" modal. The modal title reads "Add Fuel Log" with a note showing your current unit system (e.g., "Displaying in Imperial (miles, gallons, MPG)"). + +**Receipt Scanning** + +At the top of the form, click **SCAN RECEIPT** to use your camera to photograph a fuel receipt. The OCR system will automatically extract: +- Fuel amount (gallons) +- Cost per gallon +- Total cost +- Date and time +- Fuel grade +- Station name + +You can review and edit any extracted values before saving. + +**Form Fields** + +| Field | Required | Description | +|-------|----------|-------------| +| Select Vehicle | Yes | Choose which vehicle this fill-up is for | +| Date & Time | Yes | Pre-filled with the current date and time; click the calendar icon to change | +| MPG | Auto | Calculated automatically from distance and fuel amount | +| Trip Distance / Odometer Reading | One required | Toggle between entering trip distance (miles driven since last fill) or odometer reading. Click the toggle button to switch modes. | +| Fuel Type | Yes | Dropdown: Gasoline, Diesel, Electric, Hybrid, etc. | +| Fuel Grade | Optional | Dropdown: 87 (Regular), 89 (Midgrade), 91 (Premium), 93 (Premium), etc. | +| Fuel Amount | Yes | Number of gallons (or liters) purchased | +| Cost Per Gallon | Yes | Price per gallon (or liter) | +| Total Cost | Auto | Calculated from Fuel Amount x Cost Per Gallon. Displays "Enter fuel amount and cost per unit to see total cost." until both values are provided. | +| Location | Optional | Type a station name to search and select | +| Notes | Optional | Any additional notes about this fill-up | + +Click **Add Fuel Log** to save the entry. The button is disabled until all required fields are completed. + +### Receipt Scanning + +> **Pro Feature:** Receipt scanning requires a Pro or Enterprise subscription. See [Fuel Receipt Scanning (Pro)](#fuel-receipt-scanning-pro) for full details on what is extracted and the review workflow. + +The receipt scanning feature uses OCR technology: + +1. Click **SCAN RECEIPT** at the top of the Log Fuel form +2. Use your camera to photograph the receipt +3. The system extracts fuel data with confidence indicators +4. A review modal appears showing extracted values +5. Edit any incorrect values inline +6. Click **Accept** to auto-fill the form, or **Reject** to enter manually + +### Editing and Deleting Fuel Logs + +From the fuel logs table, each entry has action buttons: +- **Edit** -- Opens the fuel log in edit mode to update any fields +- **Delete** -- Removes the fuel log entry (with confirmation) + +--- + +## 5. Maintenance + +Click **Maintenance** in the sidebar. This page has two tabs: **RECORDS** and **SCHEDULES**. + +At the top is a **Vehicle** dropdown to select which vehicle you are viewing or adding maintenance for. + +### Maintenance Records + +The **RECORDS** tab shows your maintenance history for the selected vehicle. Below the list is the "Add Maintenance Record" form. + +### Adding a Maintenance Record + +The form on the RECORDS tab includes: + +**Receipt Upload** + +> **Pro Feature:** Maintenance receipt scanning requires a Pro or Enterprise subscription. See [Maintenance Receipt Scanning (Pro)](#maintenance-receipt-scanning-pro) for full details. + +Click the **ADD RECEIPT** button (dashed outline area) to upload or photograph a maintenance receipt. The OCR system can extract: +- Category and service type +- Cost +- Date +- Shop name + +**Form Fields** + +| Field | Required | Description | +|-------|----------|-------------| +| Category | Yes | Dropdown with options: Routine Maintenance, Repair, Performance Upgrade. Each category has specific subtypes. | +| Date | Yes | Pre-filled with today's date; click the calendar icon to change | +| Odometer Reading | Optional | Vehicle mileage at time of service | +| Cost | Optional | Total cost of the service (in $) | +| Shop Name | Optional | Name of the service shop | +| Notes | Optional | Additional details about the service (max 1,000 characters) | + +Click **Add Record** to save the maintenance record. + +**Maintenance Categories** + +| Category | Example Services | +|----------|-----------------| +| Routine Maintenance | Oil change, air filter, tire rotation, battery, brakes, coolant flush, transmission fluid, spark plugs, fuel filter, cabin air filter, brake fluid, detailing | +| Repair | Engine repair, transmission repair, brake repair, electrical, cooling system, suspension, steering, fuel system, body work, paint, glass | +| Performance Upgrade | Engine tuning, suspension upgrade, wheels/tires, brake upgrade, exhaust, intake, lighting, audio | + +### Maintenance Schedules + +Click the **SCHEDULES** tab to set up recurring maintenance reminders. + +### Creating a Schedule + +The "Create Maintenance Schedule" form includes: + +| Field | Required | Description | +|-------|----------|-------------| +| Category | Yes | Same categories as maintenance records | +| Schedule Type | Yes | Three options (radio buttons): | +| | | **Interval-based** -- Every X months or miles (e.g., oil change every 5,000 miles or 6 months) | +| | | **Fixed date** -- A specific calendar date | +| | | **Time since last service** -- Based on when service was last performed | +| Interval (Months) | Conditional | Number of months between services. Optional if miles are specified. | +| Interval (Miles) | Conditional | Number of miles between services. Optional if months are specified. | +| Reminders | Optional | Set up to 3 reminders (Reminder 1, Reminder 2, Reminder 3) via dropdowns | +| Email notifications | Optional | Toggle to receive email reminders when service is due | + +Click **Create Schedule** to save. + +Below the form, the "Maintenance Schedules" section lists all active schedules for the selected vehicle, showing when each service is next due. + +--- + +## 6. Gas Stations + +Click **Gas Stations** in the sidebar. This page helps you find gas stations near you and save your favorites. + +The page is split into two sections: +- **Left**: An interactive Google Map showing station locations as markers +- **Right**: Search controls + +### Finding Stations + +**Search Options** + +| Control | Description | +|---------|-------------| +| **Use Current Location** | Large red button -- uses your device's GPS to center the search on your current location | +| **Street** | Enter a street address (e.g., 123 Main St) | +| **City** | Enter a city name | +| **State** | Select from dropdown | +| **ZIP** | Enter a ZIP code | +| **Search Radius** | Slider from 1 mi to 25 mi (default: 5 mi) | +| **Search Stations** | Click to execute the search | + +You can either use your current location OR manually enter an address. Search results appear below the map. + +**Search Results** + +Below the map, there are three tabs: + +| Tab | Description | +|-----|-------------| +| **RESULTS (n)** | Stations found by your search, showing count | +| **SAVED (n)** | Your saved/favorite stations | +| **PREMIUM 93** | Stations verified to carry true 93-octane fuel | + +Each station result shows: +- Station name (e.g., "Costco Gas Station", "Mobil") +- Street address and city +- Star rating (community-verified) +- Fuel grade badges (e.g., "93 Octane - w/ Ethanol") +- Save/unsave button + +### Saved Stations + +Click the **SAVED** tab to see your favorite stations. Saved stations also appear as yellow star markers on the map. You can: +- View station details +- Remove a station from your saved list +- Navigate on the map by clicking a station card + +### Premium 93 Stations + +Click the **PREMIUM 93** tab to see your "Your Premium 93 Stations" -- stations that have been community-verified to carry genuine 93-octane fuel. This is especially useful for performance vehicles that require premium fuel. + +--- + +## 7. Documents + +Click **Documents** in the sidebar. This page stores all your vehicle-related paperwork digitally. + +### Documents Overview + +The page shows the title "Documents" with an **Add Document** button in the top-right corner. + +If you have no documents yet, you will see an empty state: +- A document icon +- "No Documents Yet" +- "You haven't added any documents yet. Documents will appear here once you create them." +- A **Go to Vehicles** button (since documents are associated with vehicles) + +When documents exist, they appear in a list/grid with preview thumbnails, titles, document types, and associated vehicles. + +### Adding a Document + +Click **Add Document** to open the "Add Document" modal with these fields: + +| Field | Required | Description | +|-------|----------|-------------| +| Vehicle | Yes | Select which vehicle this document belongs to (dropdown of your vehicles) | +| Document Type | Yes | Select the type (see Document Types below) | +| Title | Yes | A descriptive title (e.g., "Honda CBR600RR Service Manual") | +| Notes | Optional | Any additional notes about this document | +| Upload image/PDF | Yes | Click **Choose File** to upload an image or PDF file | + +Click **Create Document** to save, or **Cancel** to discard. + +### Document Types + +| Type | What to Store | +|------|--------------| +| Insurance | Insurance policies, cards, declarations pages | +| Registration | Vehicle registration documents | +| Maintenance Manual | Owner's manuals and service manuals | +| Service Records | Service history documentation from dealers/shops | +| Recall Notices | Vehicle recall notifications | +| Inspection Reports | State inspection or emissions test reports | +| Receipts | Purchase receipts for parts, accessories, services | +| Other | Any other vehicle-related document | + +**Insurance documents** have additional fields: Insurance Company, Policy Number, Effective Date, Expiration Date, Coverage amounts (Bodily Injury, Property Damage), and Premium. + +**Registration documents** have additional fields: License Plate, Expiration Date, and Registration Cost. + +Documents with expiration dates will show countdown badges so you know when renewals are coming up. + +> **Pro Feature:** When uploading a Maintenance Manual PDF, Pro and Enterprise users can check **Scan for Maintenance Schedule** to automatically extract a complete maintenance schedule from the document. See [Maintenance Manual PDF Extraction (Pro)](#maintenance-manual-pdf-extraction-pro) for the full workflow. + +--- + +## 8. Settings + +Click **Settings** in the sidebar to manage your account, preferences, and data. + +### Profile + +The Profile section displays your account information: + +| Field | Description | +|-------|-------------| +| **Avatar** | Shows your initial in a circle | +| **Name** | Your display name (e.g., "Eric Gullickson") | +| **Email** | Your account email address | +| **Account Status** | Shows "Verified account" if email is verified | +| **Display Name** | Your public-facing name | +| **Notification Email** | The email address used for notifications (defaults to "Using primary email") | + +Click the **Edit** button to update your display name or notification email. + +### Security and Privacy + +The Security & Privacy row shows "Password, two-factor authentication" with a **Manage** button. Click it to: +- Change your password +- Set up two-factor authentication +- Manage active sessions +- Log out all devices + +### My Vehicles + +A summary list of all your vehicles (with count, e.g., "My Vehicles (4)"). Click the **Manage** button to go to the Vehicles page. + +### Subscription + +Shows your current subscription plan with a **Manage** button. + +| Plan | Features | +|------|----------| +| **FREE** | Basic vehicle management, up to 2 vehicles, basic fuel tracking, document storage | +| **Pro** | Up to 10 vehicles, receipt OCR scanning, maintenance schedules, email ingestion | +| **Enterprise** | Unlimited vehicles, all Pro features | + +"Upgrade to Pro or Enterprise for more features and vehicle slots." + +Click **Manage** to view plan details, change your subscription, manage payment methods, and view billing history. + +### Notifications + +| Setting | Description | Default | +|---------|-------------|---------| +| **Push Notifications** | Receive notifications about your vehicles (maintenance due, etc.) | ON | +| **Email Updates** | Receive maintenance reminders and updates via email | OFF | + +Toggle each setting on or off. + +### Appearance and Units + +| Setting | Description | Options | +|---------|-------------|---------| +| **Dark Mode** | Use dark theme for better night viewing | Toggle ON/OFF (default: OFF) | +| **Units for distance and capacity** | Choose between measurement systems | **Imperial**: miles, gallons, MPG, USD / **Metric**: km, liters, L/100km, EUR | + +The unit system you select here applies throughout the entire application -- Dashboard, Fuel Logs, Maintenance, and Vehicle Details all update to reflect your preference. + +### Data Import and Export + +| Action | Description | Button | +|--------|-------------|--------| +| **Import Data** | Upload and restore your vehicle data from a backup file | **Import** | +| **Export Data** | Download your vehicle and fuel log data as a backup file | **Export** | + +Export creates a downloadable archive of all your data. Import accepts a previously exported backup file to restore your data. + +### Account Actions + +At the bottom of the Settings page: + +| Button | Action | +|--------|--------| +| **Sign Out** | Log out of your account | +| **DELETE ACCOUNT** | Permanently delete your account and all data. This initiates a 30-day grace period during which you can cancel the deletion by logging back in. | + +--- + +## 9. Subscription Tiers and Pro Features + +MotoVaultPro offers three subscription tiers. Higher tiers automatically include all features from lower tiers. + +### Tier Comparison + +| Feature | Free | Pro | Enterprise | +|---------|:----:|:---:|:----------:| +| **Vehicle Slots** | 2 | 5 | Unlimited | +| Vehicle management | Yes | Yes | Yes | +| Fuel log tracking | Yes | Yes | Yes | +| Document storage | Yes | Yes | Yes | +| Gas station finder | Yes | Yes | Yes | +| Maintenance records | Yes | Yes | Yes | +| Maintenance schedules | Yes | Yes | Yes | +| Data import/export | Yes | Yes | Yes | +| **VIN camera scan and decode** | -- | Yes | Yes | +| **Fuel receipt OCR scanning** | -- | Yes | Yes | +| **Maintenance receipt OCR scanning** | -- | Yes | Yes | +| **Maintenance manual PDF extraction** | -- | Yes | Yes | +| **Email ingestion** (forward receipts) | -- | Yes | Yes | +| **Shared vehicle documents** | -- | Yes | Yes | +| **Community station submissions** | -- | Yes | Yes | + +When you attempt to use a Pro feature on the Free tier, an **Upgrade Required** dialog appears explaining the feature and offering a direct link to upgrade. + +--- + +### VIN Camera Scanning and Decode (Pro) + +**What it does:** Use your device camera to photograph your vehicle's VIN plate, and the system automatically reads the VIN using OCR (Optical Character Recognition) and decodes it from the NHTSA database. + +**How to use it:** + +1. Go to **Vehicles** and click **+ Add Vehicle** +2. In the VIN Number field, click the **camera icon** +3. Point your camera at the VIN plate on your vehicle (typically on the driver-side dashboard or door jamb) +4. The OCR system reads the 17-character VIN from the image +5. A **VIN OCR Review modal** appears showing the detected VIN with confidence indicators +6. Confirm or correct the VIN, then click **Accept** +7. Click the **Decode VIN** button +8. The system queries the NHTSA database and auto-populates: Year, Make, Model, Engine, Transmission, and Trim +9. Review the pre-filled fields and complete the remaining details + +This eliminates manual data entry errors and ensures accurate vehicle specifications. + +--- + +### Fuel Receipt Scanning (Pro) + +**What it does:** Photograph a fuel receipt and the OCR system extracts all relevant data, automatically filling in your fuel log entry. + +**How to use it:** + +1. Open the **Log Fuel** modal (from Dashboard or Fuel Logs page) +2. Click the **SCAN RECEIPT** button at the top of the form +3. Use your camera to photograph the fuel receipt +4. The system processes the image and extracts: + +| Extracted Field | Description | +|----------------|-------------| +| Fuel Amount | Gallons or liters purchased | +| Cost Per Unit | Price per gallon/liter | +| Total Cost | Total transaction amount | +| Date & Time | Transaction timestamp | +| Fuel Grade | Regular, Midgrade, Premium, etc. | +| Station Name | Merchant name matched to known stations | + +5. A **Receipt OCR Review modal** appears showing all extracted values with confidence scores +6. Each field can be edited inline if the OCR got something wrong +7. The station name is automatically matched against known gas stations in the system +8. Click **Accept** to auto-fill the Log Fuel form with the extracted values +9. Click **Reject** to discard the scan and enter data manually +10. Review the pre-filled form and click **Add Fuel Log** + +**Tips for best results:** +- Photograph the receipt on a flat, well-lit surface +- Ensure the entire receipt is visible in the frame +- Avoid wrinkled or faded receipts when possible + +--- + +### Maintenance Receipt Scanning (Pro) + +**What it does:** Photograph a maintenance or service receipt to automatically extract service details into a maintenance record. + +**How to use it:** + +1. Go to **Maintenance** and select a vehicle +2. On the **RECORDS** tab, click the **ADD RECEIPT** button (dashed outline area) +3. Use your camera to photograph the service receipt +4. The system processes the image and extracts: + +| Extracted Field | Description | +|----------------|-------------| +| Category | Service type (Routine, Repair, Performance) | +| Subtypes | Specific services performed (e.g., Oil Change, Tire Rotation) | +| Cost | Total service cost | +| Date | Service date | +| Shop Name | Name of the service shop | + +5. A **Maintenance Receipt Review modal** shows extracted values with confidence indicators +6. Edit any incorrect values inline +7. Click **Accept** to auto-fill the maintenance record form +8. Review and click **Add Record** + +--- + +### Maintenance Manual PDF Extraction (Pro) + +**What it does:** Upload your vehicle's owner's manual or maintenance manual as a PDF, and the system automatically extracts the recommended maintenance schedule -- creating maintenance schedules with the correct intervals for your specific vehicle. + +**How to use it:** + +1. Go to **Documents** and click **Add Document** +2. Select your vehicle and choose **Maintenance Manual** as the document type +3. Upload the PDF file +4. Check the **Scan for Maintenance Schedule** checkbox (Pro feature -- indicated by a lock icon for Free tier users) +5. Click **Create Document** +6. The system submits the PDF for asynchronous processing +7. A progress indicator shows while the document is being analyzed +8. When processing completes, the **Maintenance Schedule Review** screen appears showing: + +| Column | Description | +|--------|-------------| +| Checkbox | Select which items to create as schedules | +| Service Name | Extracted maintenance service (e.g., "Engine Oil and Filter Change") | +| Interval | Recommended interval in months and/or miles | +| Details | Additional notes or specifications | +| Confidence | How confident the system is in the extraction (High/Medium/Low) | + +9. Check the boxes next to the maintenance items you want to create +10. Edit any service names, intervals, or details inline +11. Click **Create Selected Schedules** to batch-create all selected items as maintenance schedules for your vehicle + +This turns a 50-page owner's manual into a complete set of maintenance reminders in minutes. + +--- + +### Email Ingestion (Pro) + +**What it does:** Forward vehicle-related emails (service receipts, insurance documents, registration notices) to a dedicated email address, and they automatically appear in your MotoVaultPro account ready to be associated with a vehicle. + +**How to use it:** + +1. Forward any vehicle-related email to your dedicated MotoVaultPro ingestion address +2. The system processes the email and any attachments +3. On your **Dashboard**, a **Pending Associations** banner appears showing how many items are waiting +4. Click the banner to open the **Pending Association List** +5. For each pending item, you see: + - A preview of the document or receipt + - A vehicle selector dropdown +6. Select the correct vehicle for each item and click **Associate** +7. Or click **Discard** to remove items you do not want + +**Bulk actions** are available to discard all pending items at once. + +This is especially useful for: +- Forwarding digital receipts from auto parts stores +- Forwarding service confirmation emails from your mechanic +- Forwarding insurance policy documents from your provider +- Forwarding registration renewal notices + +--- + +### Shared Vehicle Documents (Pro) + +**What it does:** Associate a single document with multiple vehicles. Useful for fleet insurance policies, multi-vehicle service agreements, or shared maintenance contracts. + +**How to use it:** + +1. Open an existing document's detail page +2. In the **Shared Vehicles** section, click **Add Vehicle** +3. Select additional vehicles from the dropdown +4. The document now appears in the Documents section for each associated vehicle +5. To remove a vehicle association, click the **Remove** button next to that vehicle + +--- + +### Community Station Submissions (Pro) + +**What it does:** Submit new gas stations to the MotoVaultPro community database, helping other enthusiasts find quality fuel locations -- especially stations carrying true 93-octane premium fuel. + +**How to use it:** + +1. Go to **Gas Stations** +2. Look for the option to submit a new community station +3. Fill in the submission form: + +| Field | Description | +|-------|-------------| +| Station Name | Name of the gas station | +| Location | Address or location | +| Fuel Types | Available fuel types and grades | +| Amenities | Available amenities (bathrooms, ATM, convenience store, etc.) | +| Notes | Any additional information | +| Photo | Optional photo of the station | + +4. Submit for community review +5. An admin reviews and approves or rejects the submission +6. Approved stations appear in the **PREMIUM 93** tab and search results with a community-verified badge + +--- + +### Managing Your Subscription + +**Viewing Your Plan** + +Go to **Settings** and find the **Subscription** section. It shows your current plan (FREE, Pro, or Enterprise) with a **Manage** button. + +**Upgrading** + +1. Click **Manage** in the Subscription section +2. The Subscription page shows tier comparison cards with pricing +3. Toggle between **Monthly** and **Annual** billing (annual saves money) +4. Click **Upgrade** on the plan you want +5. Enter your payment details using the secure Stripe payment form +6. Your new features are available immediately + +**Payment Methods** + +- Payment is processed through Stripe (credit/debit cards) +- You can save a card for recurring billing +- Update or remove your payment method at any time +- View billing history and download invoices as PDFs + +**Billing History** + +The billing history table shows all past invoices with: +- Date +- Description +- Amount +- Status (Paid, Pending, Failed) +- Download PDF button for each invoice + +**Downgrading** + +If you downgrade from a higher tier, you may need to reduce your vehicles to fit within the lower tier's limit: + +1. Click **Downgrade** on the lower plan +2. A **Vehicle Selection dialog** appears if you exceed the new tier's vehicle limit +3. Select which vehicles to keep (e.g., keep 2 for Free tier) +4. A warning explains that removed vehicles and their data will be deleted +5. Confirm the downgrade + +| Tier | Vehicle Limit | +|------|:------------:| +| Free | 2 | +| Pro | 5 | +| Enterprise | Unlimited | + +**Cancelling** + +1. On the Subscription page, click **Cancel Subscription** +2. A confirmation dialog appears with retention options +3. Confirm cancellation +4. Your subscription remains active until the end of the current billing period +5. After expiration, your account reverts to the Free tier +6. Click **Reactivate** at any time before expiration to keep your plan + +--- + +## 10. Mobile Experience + +MotoVaultPro is fully responsive and works on both desktop and mobile devices. + +**Mobile Navigation** + +On mobile, the sidebar is replaced by: +- A **bottom navigation bar** with icons for: Dashboard, Vehicles, Stations +- A **floating action button (FAB)** in the center with quick actions: + - Log Fuel + - Add Vehicle + - Add Document + - Add Maintenance +- A **hamburger menu** (accessed from the header) that slides up from the bottom, providing access to all sections: Dashboard, Vehicles, Log Fuel, Maintenance, Documents, Settings + +**Mobile Optimizations** +- Touch-friendly buttons and targets (minimum 44px) +- Swipe gestures for image viewing +- Camera integration for VIN scanning and receipt capture +- Full-screen forms for data entry +- Responsive card layouts that stack vertically on smaller screens + +All features available on desktop are also available on mobile -- no functionality is lost on smaller screens. + +--- + +## Quick Reference + +### Keyboard Shortcuts and Tips + +- **Search vehicles** -- Use the search bar on the Vehicles page to quickly find a vehicle by name, make, model, or VIN +- **Quick fuel log** -- Click "LOG FUEL" on the Dashboard or "+ Add Fuel Log" on the Fuel Logs page +- **Switch vehicles on Maintenance** -- Use the Vehicle dropdown at the top of the Maintenance page to switch between vehicles without leaving the page + +### Common Workflows + +**Record a fuel fill-up** +1. Click **LOG FUEL** on the Dashboard (or go to Fuel Logs > + Add Fuel Log) +2. Select the vehicle +3. Enter the fuel amount and cost per gallon +4. Optionally enter trip distance or odometer reading for MPG calculation +5. Click **Add Fuel Log** + +**Schedule recurring maintenance** +1. Go to **Maintenance** +2. Select a vehicle from the dropdown +3. Click the **SCHEDULES** tab +4. Select a category and schedule type +5. Set the interval (months and/or miles) +6. Configure reminders +7. Click **Create Schedule** + +**Upload a document** +1. Go to **Documents** +2. Click **Add Document** +3. Select a vehicle and document type +4. Enter a title +5. Upload the file (image or PDF) +6. Click **Create Document** + +**Find a gas station** +1. Go to **Gas Stations** +2. Click **Use Current Location** or enter an address +3. Adjust the search radius +4. Click **Search Stations** +5. Browse results and click the save icon to bookmark your favorites + +**Export your data** +1. Go to **Settings** +2. Scroll to "Data & Storage" +3. Click **Export** +4. A backup file will download containing all your vehicle data, fuel logs, and documents + +--- + +*MotoVaultPro -- Precision Vehicle Management* +*2026 FB Technologies LLC. All rights reserved.* diff --git a/frontend/.claude/tdd-guard/data/test.json b/frontend/.claude/tdd-guard/data/test.json index af23ba1..7788694 100644 --- a/frontend/.claude/tdd-guard/data/test.json +++ b/frontend/.claude/tdd-guard/data/test.json @@ -1,5 +1,262 @@ { "testModules": [ + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/components/CommunityStationCard.test.tsx", + "tests": [ + { + "name": "should render station details", + "fullName": "CommunityStationCard should render station details", + "state": "passed" + }, + { + "name": "should display 93 octane status", + "fullName": "CommunityStationCard should display 93 octane status", + "state": "passed" + }, + { + "name": "should display price when available", + "fullName": "CommunityStationCard should display price when available", + "state": "passed" + }, + { + "name": "should display status badge", + "fullName": "CommunityStationCard should display status badge", + "state": "passed" + }, + { + "name": "should show withdraw button for user view", + "fullName": "CommunityStationCard should show withdraw button for user view", + "state": "failed", + "errors": [ + { + "message": "TestingLibraryElementError: Found multiple elements with the role \"button\"\n\nHere are the matching elements:\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n\n(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mShell Downtown\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mapproved\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m123 Main St\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDenver, CO, 80202\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mBrand: Shell\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m93 Octane · w/ Ethanol\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m$3.599/gal\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mNotes:\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGood quality fuel\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSubmitted by: \u001b[0m\n \u001b[0muser@example.com\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mNavigate\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mPremium 93\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFavorite\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at Object.getElementError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/config.js:37:19)\n at getElementError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:20:35)\n at getMultipleElementsFoundError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:23:10)\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:55:13\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/components/CommunityStationCard.test.tsx:66:19)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)", + "name": "TestingLibraryElementError" + } + ] + }, + { + "name": "should show approve and reject buttons for admin", + "fullName": "CommunityStationCard should show approve and reject buttons for admin", + "state": "passed" + }, + { + "name": "should call onWithdraw when withdraw button is clicked", + "fullName": "CommunityStationCard should call onWithdraw when withdraw button is clicked", + "state": "failed", + "errors": [ + { + "message": "TestingLibraryElementError: Found multiple elements with the role \"button\"\n\nHere are the matching elements:\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n\n(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mShell Downtown\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mapproved\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m123 Main St\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDenver, CO, 80202\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mBrand: Shell\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m93 Octane · w/ Ethanol\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m$3.599/gal\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mNotes:\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGood quality fuel\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSubmitted by: \u001b[0m\n \u001b[0muser@example.com\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mNavigate\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mPremium 93\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFavorite\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at Object.getElementError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/config.js:37:19)\n at getElementError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:20:35)\n at getMultipleElementsFoundError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:23:10)\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:55:13\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/components/CommunityStationCard.test.tsx:94:35)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)", + "name": "TestingLibraryElementError" + } + ] + }, + { + "name": "should handle rejection with reason", + "fullName": "CommunityStationCard should handle rejection with reason", + "state": "passed" + }, + { + "name": "should work on mobile viewport", + "fullName": "CommunityStationCard should work on mobile viewport", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/AdminSkeleton.test.tsx", + "tests": [ + { + "name": "should render default number of rows", + "fullName": "AdminSkeleton SkeletonRow should render default number of rows", + "state": "passed" + }, + { + "name": "should render specified number of rows", + "fullName": "AdminSkeleton SkeletonRow should render specified number of rows", + "state": "failed", + "errors": [ + { + "message": "Error: expect(received).toHaveLength(expected)\n\nExpected length: 15\nReceived length: 20\nReceived object: [, , , , , , , , , , …]\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/AdminSkeleton.test.tsx:20:63)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + }, + { + "name": "should render default number of cards", + "fullName": "AdminSkeleton SkeletonCard should render default number of cards", + "state": "passed" + }, + { + "name": "should render specified number of cards", + "fullName": "AdminSkeleton SkeletonCard should render specified number of cards", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/shared-minimal/components/VehicleLimitDialog.test.tsx", + "tests": [ + { + "name": "renders when open", + "fullName": "VehicleLimitDialog Dialog rendering renders when open", + "state": "passed" + }, + { + "name": "does not render when closed", + "fullName": "VehicleLimitDialog Dialog rendering does not render when closed", + "state": "passed" + }, + { + "name": "displays current count and limit", + "fullName": "VehicleLimitDialog Props display displays current count and limit", + "state": "passed" + }, + { + "name": "displays free tier upgrade prompt", + "fullName": "VehicleLimitDialog Props display displays free tier upgrade prompt", + "state": "passed" + }, + { + "name": "displays pro tier upgrade prompt", + "fullName": "VehicleLimitDialog Props display displays pro tier upgrade prompt", + "state": "passed" + }, + { + "name": "shows tier chips for free user", + "fullName": "VehicleLimitDialog Props display shows tier chips for free user", + "state": "passed" + }, + { + "name": "shows tier chips for pro user", + "fullName": "VehicleLimitDialog Props display shows tier chips for pro user", + "state": "passed" + }, + { + "name": "calls onClose when \"Maybe Later\" is clicked", + "fullName": "VehicleLimitDialog User interactions calls onClose when \"Maybe Later\" is clicked", + "state": "passed" + }, + { + "name": "calls onClose when \"Upgrade (Coming Soon)\" is clicked", + "fullName": "VehicleLimitDialog User interactions calls onClose when \"Upgrade (Coming Soon)\" is clicked", + "state": "passed" + }, + { + "name": "renders fullscreen on mobile", + "fullName": "VehicleLimitDialog Mobile responsiveness renders fullscreen on mobile", + "state": "failed", + "errors": [ + { + "message": "Error: expect(received).toBeInTheDocument()\n\nreceived value must be an HTMLElement or an SVGElement.\nReceived has value: null\n at __EXTERNAL_MATCHER_TRAP__ (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/expect/build/index.js:325:30)\n at Object.throwingMatcher [as toBeInTheDocument] (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/expect/build/index.js:326:15)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/shared-minimal/components/VehicleLimitDialog.test.tsx:185:22)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + }, + { + "name": "shows close button on mobile", + "fullName": "VehicleLimitDialog Mobile responsiveness shows close button on mobile", + "state": "passed" + }, + { + "name": "hides close button on desktop", + "fullName": "VehicleLimitDialog Mobile responsiveness hides close button on desktop", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/DocumentCardMetadata.test.tsx", + "tests": [ + { + "name": "displays expiration date", + "fullName": "DocumentCardMetadata insurance documents displays expiration date", + "state": "passed" + }, + { + "name": "displays policy number", + "fullName": "DocumentCardMetadata insurance documents displays policy number", + "state": "passed" + }, + { + "name": "displays insurance company", + "fullName": "DocumentCardMetadata insurance documents displays insurance company", + "state": "passed" + }, + { + "name": "limits to 3 fields in card variant", + "fullName": "DocumentCardMetadata insurance documents limits to 3 fields in card variant", + "state": "passed" + }, + { + "name": "shows all fields in detail variant", + "fullName": "DocumentCardMetadata insurance documents shows all fields in detail variant", + "state": "passed" + }, + { + "name": "displays expiration date", + "fullName": "DocumentCardMetadata registration documents displays expiration date", + "state": "passed" + }, + { + "name": "displays license plate", + "fullName": "DocumentCardMetadata registration documents displays license plate", + "state": "passed" + }, + { + "name": "shows cost in detail variant only", + "fullName": "DocumentCardMetadata registration documents shows cost in detail variant only", + "state": "passed" + }, + { + "name": "displays issued date if set", + "fullName": "DocumentCardMetadata manual documents displays issued date if set", + "state": "passed" + }, + { + "name": "shows notes preview in detail variant only", + "fullName": "DocumentCardMetadata manual documents shows notes preview in detail variant only", + "state": "passed" + }, + { + "name": "truncates long notes in detail variant", + "fullName": "DocumentCardMetadata manual documents truncates long notes in detail variant", + "state": "passed" + }, + { + "name": "returns null when no metadata to display", + "fullName": "DocumentCardMetadata empty states returns null when no metadata to display", + "state": "passed" + }, + { + "name": "handles missing details gracefully", + "fullName": "DocumentCardMetadata empty states handles missing details gracefully", + "state": "passed" + }, + { + "name": "uses text-xs for mobile variant", + "fullName": "DocumentCardMetadata variant styling uses text-xs for mobile variant", + "state": "passed" + }, + { + "name": "uses text-sm for card variant", + "fullName": "DocumentCardMetadata variant styling uses text-sm for card variant", + "state": "passed" + }, + { + "name": "uses grid layout for detail variant", + "fullName": "DocumentCardMetadata variant styling uses grid layout for detail variant", + "state": "passed" + }, + { + "name": "formats premium correctly", + "fullName": "DocumentCardMetadata currency formatting formats premium correctly", + "state": "passed" + }, + { + "name": "handles string numbers", + "fullName": "DocumentCardMetadata currency formatting handles string numbers", + "state": "passed" + } + ] + }, { "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/DashboardScreen.test.tsx", "tests": [ @@ -26,66 +283,97 @@ ] }, { - "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/ActionBar.test.tsx", + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/BulkActionDialog.test.tsx", "tests": [ { - "name": "renders both buttons with correct text", - "fullName": "ActionBar renders both buttons with correct text", + "name": "should render dialog when open", + "fullName": "BulkActionDialog should render dialog when open", "state": "passed" }, { - "name": "calls onAddVehicle when Add Vehicle button clicked", - "fullName": "ActionBar calls onAddVehicle when Add Vehicle button clicked", + "name": "should display list of items", + "fullName": "BulkActionDialog should display list of items", "state": "passed" }, { - "name": "calls onLogFuel when Log Fuel button clicked", - "fullName": "ActionBar calls onLogFuel when Log Fuel button clicked", + "name": "should call onConfirm when confirm button clicked", + "fullName": "BulkActionDialog should call onConfirm when confirm button clicked", + "state": "passed" + }, + { + "name": "should call onCancel when cancel button clicked", + "fullName": "BulkActionDialog should call onCancel when cancel button clicked", + "state": "passed" + }, + { + "name": "should disable buttons when loading", + "fullName": "BulkActionDialog should disable buttons when loading", + "state": "failed", + "errors": [ + { + "message": "TestingLibraryElementError: Unable to find an accessible element with the role \"button\" and name `/confirm/i`\n\nHere are the accessible roles:\n\n presentation:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n dialog:\n\n Name \"Delete Items?\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n heading:\n\n Name \"Delete Items?\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n paragraph:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n list:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n listitem:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n button:\n\n Name \"Cancel\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n progressbar:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDelete Items?\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mThis action cannot be undone.\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mItem 1\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mItem 2\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mItem 3\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mCancel\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at Object.getElementError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/config.js:37:19)\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:76:38\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:52:17\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/BulkActionDialog.test.tsx:55:34)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)", + "name": "TestingLibraryElementError" + } + ] + }, + { + "name": "should show loading spinner when loading", + "fullName": "BulkActionDialog should show loading spinner when loading", + "state": "failed", + "errors": [ + { + "message": "Error: expect(received).toBeInTheDocument()\n\nreceived value must be an HTMLElement or an SVGElement.\nReceived has value: null\n at __EXTERNAL_MATCHER_TRAP__ (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/expect/build/index.js:325:30)\n at Object.throwingMatcher [as toBeInTheDocument] (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/expect/build/index.js:326:15)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/BulkActionDialog.test.tsx:67:66)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + }, + { + "name": "should support custom button text", + "fullName": "BulkActionDialog should support custom button text", "state": "passed" } ] }, { - "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/VehicleRosterCard.test.tsx", + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/useAdminAccess.test.tsx", "tests": [ { - "name": "renders vehicle label with year make model", - "fullName": "VehicleRosterCard renders vehicle label with year make model", + "name": "should return loading state initially", + "fullName": "useAdminAccess should return loading state initially", "state": "passed" }, { - "name": "renders health badge with correct color class for green health", - "fullName": "VehicleRosterCard renders health badge with correct color class for green health", - "state": "passed" + "name": "should return isAdmin true when user is admin", + "fullName": "useAdminAccess should return isAdmin true when user is admin", + "state": "failed", + "errors": [ + { + "message": "Error: expect(received).toBeUndefined()\n\nReceived: null\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/useAdminAccess.test.tsx:76:34)" + } + ] }, { - "name": "renders health badge with correct color class for yellow health", - "fullName": "VehicleRosterCard renders health badge with correct color class for yellow health", - "state": "passed" + "name": "should return isAdmin false when user is not admin", + "fullName": "useAdminAccess should return isAdmin false when user is not admin", + "state": "failed", + "errors": [ + { + "message": "Error: expect(received).toBeUndefined()\n\nReceived: null\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/useAdminAccess.test.tsx:98:34)" + } + ] }, { - "name": "renders health badge with correct color class for red health", - "fullName": "VehicleRosterCard renders health badge with correct color class for red health", - "state": "passed" + "name": "should handle errors gracefully", + "fullName": "useAdminAccess should handle errors gracefully", + "state": "failed", + "errors": [ + { + "message": "Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: false\nReceived: true\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/useAdminAccess.test.tsx:114:56\n at runWithExpensiveErrorDiagnosticsDisabled (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/config.js:47:12)\n at checkCallback (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/wait-for.js:124:77)\n at checkRealTimersCallback (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/wait-for.js:118:16)\n at Timeout.task [as _onTimeout] (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)\n at listOnTimeout (node:internal/timers:605:17)\n at processTimers (node:internal/timers:541:7)" + } + ] }, { - "name": "renders attention items text", - "fullName": "VehicleRosterCard renders attention items text", - "state": "passed" - }, - { - "name": "renders odometer with formatting", - "fullName": "VehicleRosterCard renders odometer with formatting", - "state": "passed" - }, - { - "name": "calls onClick with vehicle ID when clicked", - "fullName": "VehicleRosterCard calls onClick with vehicle ID when clicked", - "state": "passed" - }, - { - "name": "renders All clear when no attention items", - "fullName": "VehicleRosterCard renders All clear when no attention items", + "name": "should not query when user is not authenticated", + "fullName": "useAdminAccess should not query when user is not authenticated", "state": "passed" } ] @@ -164,8 +452,945 @@ "state": "passed" } ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/ExpirationBadge.test.tsx", + "tests": [ + { + "name": "renders nothing for null", + "fullName": "ExpirationBadge when no expiration date is provided renders nothing for null", + "state": "passed" + }, + { + "name": "renders nothing for undefined", + "fullName": "ExpirationBadge when no expiration date is provided renders nothing for undefined", + "state": "passed" + }, + { + "name": "renders nothing for empty string", + "fullName": "ExpirationBadge when no expiration date is provided renders nothing for empty string", + "state": "passed" + }, + { + "name": "shows \"Expired\" badge for past dates", + "fullName": "ExpirationBadge when document is expired shows \"Expired\" badge for past dates", + "state": "passed" + }, + { + "name": "shows \"Expired\" badge for dates far in the past", + "fullName": "ExpirationBadge when document is expired shows \"Expired\" badge for dates far in the past", + "state": "passed" + }, + { + "name": "has red styling for expired badge", + "fullName": "ExpirationBadge when document is expired has red styling for expired badge", + "state": "passed" + }, + { + "name": "shows \"Expires today\" badge", + "fullName": "ExpirationBadge when document expires today shows \"Expires today\" badge", + "state": "passed" + }, + { + "name": "has amber styling for expiring soon badge", + "fullName": "ExpirationBadge when document expires today has amber styling for expiring soon badge", + "state": "passed" + }, + { + "name": "shows \"Expires tomorrow\" badge", + "fullName": "ExpirationBadge when document expires tomorrow shows \"Expires tomorrow\" badge", + "state": "passed" + }, + { + "name": "shows \"Expires in X days\" badge for 15 days", + "fullName": "ExpirationBadge when document expires within 30 days shows \"Expires in X days\" badge for 15 days", + "state": "passed" + }, + { + "name": "shows \"Expires in X days\" badge for 30 days", + "fullName": "ExpirationBadge when document expires within 30 days shows \"Expires in X days\" badge for 30 days", + "state": "passed" + }, + { + "name": "shows \"Expires in X days\" badge for 2 days", + "fullName": "ExpirationBadge when document expires within 30 days shows \"Expires in X days\" badge for 2 days", + "state": "passed" + }, + { + "name": "renders nothing for 31 days out", + "fullName": "ExpirationBadge when document expires after 30 days renders nothing for 31 days out", + "state": "passed" + }, + { + "name": "renders nothing for dates far in the future", + "fullName": "ExpirationBadge when document expires after 30 days renders nothing for dates far in the future", + "state": "passed" + }, + { + "name": "applies custom className to the badge", + "fullName": "ExpirationBadge className prop applies custom className to the badge", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/AdminSectionHeader.test.tsx", + "tests": [ + { + "name": "should render with title and stats", + "fullName": "AdminSectionHeader should render with title and stats", + "state": "passed" + }, + { + "name": "should render with empty stats", + "fullName": "AdminSectionHeader should render with empty stats", + "state": "passed" + }, + { + "name": "should format large numbers with locale", + "fullName": "AdminSectionHeader should format large numbers with locale", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/shared/components/CameraCapture/CameraCapture.test.tsx", + "tests": [ + { + "name": "shows loading state while requesting permission", + "fullName": "CameraCapture Permission handling shows loading state while requesting permission", + "state": "passed" + }, + { + "name": "shows error when permission denied", + "fullName": "CameraCapture Permission handling shows error when permission denied", + "state": "passed" + }, + { + "name": "shows error when camera unavailable", + "fullName": "CameraCapture Permission handling shows error when camera unavailable", + "state": "passed" + }, + { + "name": "shows viewfinder when camera access granted", + "fullName": "CameraCapture Viewfinder shows viewfinder when camera access granted", + "state": "passed" + }, + { + "name": "shows cancel button in viewfinder", + "fullName": "CameraCapture Viewfinder shows cancel button in viewfinder", + "state": "passed" + }, + { + "name": "calls onCancel when cancel button clicked", + "fullName": "CameraCapture Viewfinder calls onCancel when cancel button clicked", + "state": "passed" + }, + { + "name": "shows VIN guidance when guidanceType is vin", + "fullName": "CameraCapture Guidance overlay shows VIN guidance when guidanceType is vin", + "state": "passed" + }, + { + "name": "shows receipt guidance when guidanceType is receipt", + "fullName": "CameraCapture Guidance overlay shows receipt guidance when guidanceType is receipt", + "state": "passed" + }, + { + "name": "shows upload file button in viewfinder", + "fullName": "CameraCapture File fallback shows upload file button in viewfinder", + "state": "passed" + }, + { + "name": "switches to file fallback when upload file clicked", + "fullName": "CameraCapture File fallback switches to file fallback when upload file clicked", + "state": "passed" + }, + { + "name": "renders upload area", + "fullName": "FileInputFallback renders upload area", + "state": "passed" + }, + { + "name": "shows accepted formats", + "fullName": "FileInputFallback shows accepted formats", + "state": "passed" + }, + { + "name": "shows max file size", + "fullName": "FileInputFallback shows max file size", + "state": "passed" + }, + { + "name": "calls onCancel when cancel clicked", + "fullName": "FileInputFallback calls onCancel when cancel clicked", + "state": "passed" + }, + { + "name": "shows error for invalid file type", + "fullName": "FileInputFallback shows error for invalid file type", + "state": "passed" + }, + { + "name": "shows error for file too large", + "fullName": "FileInputFallback shows error for file too large", + "state": "passed" + }, + { + "name": "calls onFileSelect with valid file", + "fullName": "FileInputFallback calls onFileSelect with valid file", + "state": "passed" + }, + { + "name": "renders nothing when type is none", + "fullName": "GuidanceOverlay renders nothing when type is none", + "state": "passed" + }, + { + "name": "renders VIN guidance with correct description", + "fullName": "GuidanceOverlay renders VIN guidance with correct description", + "state": "passed" + }, + { + "name": "renders receipt guidance with correct description", + "fullName": "GuidanceOverlay renders receipt guidance with correct description", + "state": "passed" + }, + { + "name": "renders document guidance with correct description", + "fullName": "GuidanceOverlay renders document guidance with correct description", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/SelectionToolbar.test.tsx", + "tests": [ + { + "name": "should not render when selectedCount is 0", + "fullName": "SelectionToolbar should not render when selectedCount is 0", + "state": "passed" + }, + { + "name": "should render when items are selected", + "fullName": "SelectionToolbar should render when items are selected", + "state": "passed" + }, + { + "name": "should call onClear when Clear button clicked", + "fullName": "SelectionToolbar should call onClear when Clear button clicked", + "state": "passed" + }, + { + "name": "should call onSelectAll when Select All button clicked", + "fullName": "SelectionToolbar should call onSelectAll when Select All button clicked", + "state": "passed" + }, + { + "name": "should render custom action buttons", + "fullName": "SelectionToolbar should render custom action buttons", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/components/StationCard.test.tsx", + "tests": [ + { + "name": "should render station name and address", + "fullName": "StationCard Rendering should render station name and address", + "state": "passed" + }, + { + "name": "should render station photo if available", + "fullName": "StationCard Rendering should render station photo if available", + "state": "passed" + }, + { + "name": "should render rating when available", + "fullName": "StationCard Rendering should render rating when available", + "state": "passed" + }, + { + "name": "should render distance chip", + "fullName": "StationCard Rendering should render distance chip", + "state": "passed" + }, + { + "name": "should not crash when photo is missing", + "fullName": "StationCard Rendering should not crash when photo is missing", + "state": "passed" + }, + { + "name": "should call onSave when bookmark button clicked (not saved)", + "fullName": "StationCard Save/Delete Actions should call onSave when bookmark button clicked (not saved)", + "state": "passed" + }, + { + "name": "should call onDelete when bookmark button clicked (saved)", + "fullName": "StationCard Save/Delete Actions should call onDelete when bookmark button clicked (saved)", + "state": "passed" + }, + { + "name": "should show filled bookmark icon when saved", + "fullName": "StationCard Save/Delete Actions should show filled bookmark icon when saved", + "state": "passed" + }, + { + "name": "should show outline bookmark icon when not saved", + "fullName": "StationCard Save/Delete Actions should show outline bookmark icon when not saved", + "state": "passed" + }, + { + "name": "should open Google Maps when directions button clicked", + "fullName": "StationCard Directions Link should open Google Maps when directions button clicked", + "state": "failed", + "errors": [ + { + "message": "Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)\n\nExpected: StringContaining \"google.com/maps\", \"_blank\"\n\nNumber of calls: 0\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/components/StationCard.test.tsx:120:27)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + }, + { + "name": "should encode address in directions URL", + "fullName": "StationCard Directions Link should encode address in directions URL", + "state": "failed", + "errors": [ + { + "message": "Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)\n\nExpected: StringContaining \"123%20Main%20St%2C%20San%20Francisco%2C%20CA%2094105\", \"_blank\"\n\nNumber of calls: 0\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/components/StationCard.test.tsx:132:27)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + }, + { + "name": "should have minimum 44px button heights", + "fullName": "StationCard Touch Targets should have minimum 44px button heights", + "state": "passed" + }, + { + "name": "should call onSelect when card is clicked", + "fullName": "StationCard Card Selection should call onSelect when card is clicked", + "state": "passed" + }, + { + "name": "should not call onSelect when button is clicked", + "fullName": "StationCard Card Selection should not call onSelect when button is clicked", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/hooks/useBulkSelection.test.ts", + "tests": [ + { + "name": "should initialize with empty selection", + "fullName": "useBulkSelection should initialize with empty selection", + "state": "passed" + }, + { + "name": "should toggle individual item selection", + "fullName": "useBulkSelection should toggle individual item selection", + "state": "passed" + }, + { + "name": "should toggle all items", + "fullName": "useBulkSelection should toggle all items", + "state": "passed" + }, + { + "name": "should reset all selections", + "fullName": "useBulkSelection should reset all selections", + "state": "passed" + }, + { + "name": "should return selected items", + "fullName": "useBulkSelection should return selected items", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/useAdmins.test.tsx", + "tests": [ + { + "name": "should fetch admin users", + "fullName": "Admin user management hooks useAdmins should fetch admin users", + "state": "passed" + }, + { + "name": "should create admin and show success toast", + "fullName": "Admin user management hooks useCreateAdmin should create admin and show success toast", + "state": "passed" + }, + { + "name": "should handle create admin error", + "fullName": "Admin user management hooks useCreateAdmin should handle create admin error", + "state": "passed" + }, + { + "name": "should revoke admin and show success toast", + "fullName": "Admin user management hooks useRevokeAdmin should revoke admin and show success toast", + "state": "passed" + }, + { + "name": "should reinstate admin and show success toast", + "fullName": "Admin user management hooks useReinstateAdmin should reinstate admin and show success toast", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/shared/components/CameraCapture/__tests__/getInitialCropForGuidance.test.ts", + "tests": [ + { + "name": "returns undefined for \"none\" guidance type", + "fullName": "getInitialCropForGuidance returns undefined for \"none\" guidance type", + "state": "passed" + }, + { + "name": "returns centered 85%-width crop with 6:1 AR for VIN", + "fullName": "getInitialCropForGuidance returns centered 85%-width crop with 6:1 AR for VIN", + "state": "passed" + }, + { + "name": "returns centered 70%-height crop with 2:3 AR for receipt", + "fullName": "getInitialCropForGuidance returns centered 70%-height crop with 2:3 AR for receipt", + "state": "passed" + }, + { + "name": "returns centered 70%-height crop with 8.5:11 AR for document", + "fullName": "getInitialCropForGuidance returns centered 70%-height crop with 8.5:11 AR for document", + "state": "passed" + }, + { + "name": "returns crop areas within 0-100 bounds for all types", + "fullName": "getInitialCropForGuidance returns crop areas within 0-100 bounds for all types", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/VehicleRosterCard.test.tsx", + "tests": [ + { + "name": "renders vehicle label with year make model", + "fullName": "VehicleRosterCard renders vehicle label with year make model", + "state": "passed" + }, + { + "name": "renders health badge with correct color class for green health", + "fullName": "VehicleRosterCard renders health badge with correct color class for green health", + "state": "passed" + }, + { + "name": "renders health badge with correct color class for yellow health", + "fullName": "VehicleRosterCard renders health badge with correct color class for yellow health", + "state": "passed" + }, + { + "name": "renders health badge with correct color class for red health", + "fullName": "VehicleRosterCard renders health badge with correct color class for red health", + "state": "passed" + }, + { + "name": "renders attention items text", + "fullName": "VehicleRosterCard renders attention items text", + "state": "passed" + }, + { + "name": "renders odometer with formatting", + "fullName": "VehicleRosterCard renders odometer with formatting", + "state": "passed" + }, + { + "name": "calls onClick with vehicle ID when clicked", + "fullName": "VehicleRosterCard calls onClick with vehicle ID when clicked", + "state": "passed" + }, + { + "name": "renders All clear when no attention items", + "fullName": "VehicleRosterCard renders All clear when no attention items", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/catalogShared.test.ts", + "tests": [ + { + "name": "describes dependent counts for makes", + "fullName": "getCascadeSummary describes dependent counts for makes", + "state": "passed" + }, + { + "name": "returns empty string when nothing selected", + "fullName": "getCascadeSummary returns empty string when nothing selected", + "state": "passed" + }, + { + "name": "prefills parent context for create operations", + "fullName": "buildDefaultValues prefills parent context for create operations", + "state": "passed" + }, + { + "name": "hydrates existing entity data for editing engines", + "fullName": "buildDefaultValues hydrates existing entity data for editing engines", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/utils/navigation-links.test.ts", + "tests": [ + { + "name": "uses coordinates when valid", + "fullName": "buildNavigationLinks uses coordinates when valid", + "state": "passed" + }, + { + "name": "falls back to query when coordinates are missing", + "fullName": "buildNavigationLinks falls back to query when coordinates are missing", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/dashboard/components/__tests__/ActionBar.test.tsx", + "tests": [ + { + "name": "renders both buttons with correct text", + "fullName": "ActionBar renders both buttons with correct text", + "state": "passed" + }, + { + "name": "calls onAddVehicle when Add Vehicle button clicked", + "fullName": "ActionBar calls onAddVehicle when Add Vehicle button clicked", + "state": "passed" + }, + { + "name": "calls onLogFuel when Log Fuel button clicked", + "fullName": "ActionBar calls onLogFuel when Log Fuel button clicked", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/api/community-stations.api.test.ts", + "tests": [ + { + "name": "Module failed to load (Error)", + "fullName": "Module failed to load (Error)", + "state": "failed", + "errors": [ + { + "message": "Cannot use 'import.meta' outside a module", + "name": "Error", + "stack": "Jest encountered an unexpected token\n\nJest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.\n\nOut of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\n\nBy default \"node_modules\" folder is ignored by transformers.\n\nHere's what you can do:\n • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.\n • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript\n • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n • If you need a custom transformation specify a \"transform\" option in your config.\n • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\nYou'll find more details and examples of these config options in the docs:\nhttps://jestjs.io/docs/configuration\nFor information about custom transformations, see:\nhttps://jestjs.io/docs/code-transformation\n\nDetails:\n\n/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/core/api/client.ts:46\nconst API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';\n ^^^^\n\nSyntaxError: Cannot use 'import.meta' outside a module\n at new Script (node:vm:117:7)\n at Runtime.createScriptFromCode (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1505:14)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1399:25)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/api/community-stations.api.ts:5:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/api/community-stations.api.test.ts:6:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:77:13)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/api/stations.api.test.ts", + "tests": [ + { + "name": "Module failed to load (Error)", + "fullName": "Module failed to load (Error)", + "state": "failed", + "errors": [ + { + "message": "Cannot use 'import.meta' outside a module", + "name": "Error", + "stack": "Jest encountered an unexpected token\n\nJest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.\n\nOut of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\n\nBy default \"node_modules\" folder is ignored by transformers.\n\nHere's what you can do:\n • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.\n • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript\n • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n • If you need a custom transformation specify a \"transform\" option in your config.\n • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\nYou'll find more details and examples of these config options in the docs:\nhttps://jestjs.io/docs/configuration\nFor information about custom transformations, see:\nhttps://jestjs.io/docs/code-transformation\n\nDetails:\n\n/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/core/api/client.ts:46\nconst API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';\n ^^^^\n\nSyntaxError: Cannot use 'import.meta' outside a module\n at new Script (node:vm:117:7)\n at Runtime.createScriptFromCode (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1505:14)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1399:25)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/api/stations.api.ts:5:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/api/stations.api.test.ts:6:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:77:13)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/hooks/useStationsSearch.test.ts", + "tests": [ + { + "name": "Module failed to load (Error)", + "fullName": "Module failed to load (Error)", + "state": "failed", + "errors": [ + { + "message": "Cannot use 'import.meta' outside a module", + "name": "Error", + "stack": "Jest encountered an unexpected token\n\nJest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.\n\nOut of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\n\nBy default \"node_modules\" folder is ignored by transformers.\n\nHere's what you can do:\n • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.\n • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript\n • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n • If you need a custom transformation specify a \"transform\" option in your config.\n • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\nYou'll find more details and examples of these config options in the docs:\nhttps://jestjs.io/docs/configuration\nFor information about custom transformations, see:\nhttps://jestjs.io/docs/code-transformation\n\nDetails:\n\n/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/core/api/client.ts:46\nconst API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';\n ^^^^\n\nSyntaxError: Cannot use 'import.meta' outside a module\n at new Script (node:vm:117:7)\n at Runtime.createScriptFromCode (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1505:14)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1399:25)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/api/stations.api.ts:5:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime._generateMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1690:34)\n at Runtime.requireMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:996:39)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1046:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/hooks/useStationsSearch.ts:6:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/hooks/useStationsSearch.test.ts:8:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:77:13)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/hooks/useCommunityStations.test.ts", + "tests": [ + { + "name": "Module failed to load (Error)", + "fullName": "Module failed to load (Error)", + "state": "failed", + "errors": [ + { + "message": "Cannot use 'import.meta' outside a module", + "name": "Error", + "stack": "Jest encountered an unexpected token\n\nJest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.\n\nOut of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\n\nBy default \"node_modules\" folder is ignored by transformers.\n\nHere's what you can do:\n • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.\n • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript\n • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n • If you need a custom transformation specify a \"transform\" option in your config.\n • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\nYou'll find more details and examples of these config options in the docs:\nhttps://jestjs.io/docs/configuration\nFor information about custom transformations, see:\nhttps://jestjs.io/docs/code-transformation\n\nDetails:\n\n/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/core/api/client.ts:46\nconst API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';\n ^^^^\n\nSyntaxError: Cannot use 'import.meta' outside a module\n at new Script (node:vm:117:7)\n at Runtime.createScriptFromCode (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1505:14)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1399:25)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/api/community-stations.api.ts:5:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime._generateMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1690:34)\n at Runtime.requireMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:996:39)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1046:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/hooks/useCommunityStations.ts:6:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/stations/__tests__/hooks/useCommunityStations.test.ts:8:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:77:13)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/pages/__tests__/GuidePage.test.tsx", + "tests": [ + { + "name": "renders page heading and subheading", + "fullName": "GuidePage renders page heading and subheading", + "state": "passed" + }, + { + "name": "renders all 10 section headings", + "fullName": "GuidePage renders all 10 section headings", + "state": "passed" + }, + { + "name": "renders TOC with correct section titles", + "fullName": "GuidePage renders TOC with correct section titles", + "state": "passed" + }, + { + "name": "renders navigation bar with Guide link highlighted", + "fullName": "GuidePage renders navigation bar with Guide link highlighted", + "state": "passed" + }, + { + "name": "renders GuideScreenshot components with loading=\"lazy\"", + "fullName": "GuidePage renders GuideScreenshot components with loading=\"lazy\"", + "state": "passed" + }, + { + "name": "renders footer with copyright", + "fullName": "GuidePage renders footer with copyright", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/mobile/DocumentsMobileScreen.test.tsx", + "tests": [ + { + "name": "Module failed to load (Error)", + "fullName": "Module failed to load (Error)", + "state": "failed", + "errors": [ + { + "message": "Cannot use 'import.meta' outside a module", + "name": "Error", + "stack": "Jest encountered an unexpected token\n\nJest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.\n\nOut of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\n\nBy default \"node_modules\" folder is ignored by transformers.\n\nHere's what you can do:\n • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.\n • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript\n • To have some of your \"node_modules\" files transformed, you can specify a custom \"transformIgnorePatterns\" in your config.\n • If you need a custom transformation specify a \"transform\" option in your config.\n • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the \"moduleNameMapper\" config option.\n\nYou'll find more details and examples of these config options in the docs:\nhttps://jestjs.io/docs/configuration\nFor information about custom transformations, see:\nhttps://jestjs.io/docs/code-transformation\n\nDetails:\n\n/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/core/api/client.ts:46\nconst API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';\n ^^^^\n\nSyntaxError: Cannot use 'import.meta' outside a module\n at new Script (node:vm:117:7)\n at Runtime.createScriptFromCode (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1505:14)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1399:25)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/core/hooks/useTierAccess.ts:8:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/shared-minimal/components/UpgradeRequiredDialog.tsx:22:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/DocumentForm.tsx:3:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/AddDocumentDialog.tsx:3:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/mobile/DocumentsMobileScreen.tsx:9:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at Runtime.requireModuleOrMock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1048:21)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/mobile/DocumentsMobileScreen.test.tsx:8:1)\n at Runtime._execModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1439:24)\n at Runtime._loadModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:1022:12)\n at Runtime.requireModule (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runtime/build/index.js:882:12)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:77:13)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/EmptyState.test.tsx", + "tests": [ + { + "name": "should render with title and description", + "fullName": "EmptyState should render with title and description", + "state": "passed" + }, + { + "name": "should render with icon", + "fullName": "EmptyState should render with icon", + "state": "passed" + }, + { + "name": "should render action button when provided", + "fullName": "EmptyState should render action button when provided", + "state": "passed" + }, + { + "name": "should not render action button when not provided", + "fullName": "EmptyState should not render action button when not provided", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/DocumentPreview.test.tsx", + "tests": [ + { + "name": "should render PDF preview for PDF documents", + "fullName": "DocumentPreview PDF Preview should render PDF preview for PDF documents", + "state": "failed", + "errors": [ + { + "message": "Error: Unable to find role=\"application\" and name \"PDF Preview\"\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOpen PDF\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n\u001b[36m\u001b[39m\n at waitForWrapper (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/wait-for.js:163:27)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/DocumentPreview.test.tsx:82:20)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)", + "name": "TestingLibraryElementError" + } + ] + }, + { + "name": "should provide fallback link for PDF when object fails", + "fullName": "DocumentPreview PDF Preview should provide fallback link for PDF when object fails", + "state": "passed" + }, + { + "name": "should render image preview for image documents", + "fullName": "DocumentPreview Image Preview should render image preview for image documents", + "state": "passed" + }, + { + "name": "should have proper image styling", + "fullName": "DocumentPreview Image Preview should have proper image styling", + "state": "failed", + "errors": [ + { + "message": "Error: expect(element).toHaveClass(\"max-w-full h-auto rounded-lg border\")\n\nExpected the element to have class:\n max-w-full h-auto rounded-lg border\nReceived:\n w-full h-auto object-contain pointer-events-none\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mPinch to zoom • Double-tap to zoom • Drag to pan\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/DocumentPreview.test.tsx:138:23\n at runWithExpensiveErrorDiagnosticsDisabled (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/config.js:47:12)\n at checkCallback (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/wait-for.js:124:77)\n at checkRealTimersCallback (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/wait-for.js:118:16)\n at Timeout.task [as _onTimeout] (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)\n at listOnTimeout (node:internal/timers:605:17)\n at processTimers (node:internal/timers:541:7)" + } + ] + }, + { + "name": "should show no preview message for non-previewable documents", + "fullName": "DocumentPreview Non-previewable Documents should show no preview message for non-previewable documents", + "state": "passed" + }, + { + "name": "should not create blob URL for non-previewable documents", + "fullName": "DocumentPreview Non-previewable Documents should not create blob URL for non-previewable documents", + "state": "passed" + }, + { + "name": "should show error message when download fails", + "fullName": "DocumentPreview Error Handling should show error message when download fails", + "state": "passed" + }, + { + "name": "should handle network errors gracefully", + "fullName": "DocumentPreview Error Handling should handle network errors gracefully", + "state": "passed" + }, + { + "name": "should detect PDF from content type", + "fullName": "DocumentPreview Content Type Detection should detect PDF from content type", + "state": "passed" + }, + { + "name": "should detect images from content type", + "fullName": "DocumentPreview Content Type Detection should detect images from content type", + "state": "passed" + }, + { + "name": "should handle PNG images", + "fullName": "DocumentPreview Content Type Detection should handle PNG images", + "state": "passed" + }, + { + "name": "should handle documents with undefined content type", + "fullName": "DocumentPreview Content Type Detection should handle documents with undefined content type", + "state": "failed", + "errors": [ + { + "message": "TestingLibraryElementError: Unable to find an element with the text: No preview available.. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mLoading preview...\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at Object.getElementError (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/config.js:37:19)\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:76:38\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:52:17\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/documents/components/DocumentPreview.test.tsx:217:21)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at runNextTicks (node:internal/process/task_queues:65:5)\n at listOnTimeout (node:internal/timers:567:9)\n at processTimers (node:internal/timers:541:7)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)", + "name": "TestingLibraryElementError" + } + ] + }, + { + "name": "should clean up blob URL on unmount", + "fullName": "DocumentPreview Memory Management should clean up blob URL on unmount", + "state": "passed" + }, + { + "name": "should clean up blob URL when document changes", + "fullName": "DocumentPreview Memory Management should clean up blob URL when document changes", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/maintenance/components/MaintenanceScheduleReviewScreen.test.tsx", + "tests": [ + { + "name": "should render extracted items with checkboxes", + "fullName": "MaintenanceScheduleReviewScreen Rendering should render extracted items with checkboxes", + "state": "passed" + }, + { + "name": "should display interval information", + "fullName": "MaintenanceScheduleReviewScreen Rendering should display interval information", + "state": "passed" + }, + { + "name": "should display details text when present", + "fullName": "MaintenanceScheduleReviewScreen Rendering should display details text when present", + "state": "passed" + }, + { + "name": "should display subtypes in SubtypeCheckboxGroup", + "fullName": "MaintenanceScheduleReviewScreen Rendering should display subtypes in SubtypeCheckboxGroup", + "state": "passed" + }, + { + "name": "should toggle item selection on checkbox click", + "fullName": "MaintenanceScheduleReviewScreen Selection should toggle item selection on checkbox click", + "state": "passed" + }, + { + "name": "should deselect all items", + "fullName": "MaintenanceScheduleReviewScreen Selection should deselect all items", + "state": "passed" + }, + { + "name": "should select all items after deselecting", + "fullName": "MaintenanceScheduleReviewScreen Selection should select all items after deselecting", + "state": "passed" + }, + { + "name": "should disable create button when no items selected", + "fullName": "MaintenanceScheduleReviewScreen Selection should disable create button when no items selected", + "state": "passed" + }, + { + "name": "should show no items found message for empty extraction", + "fullName": "MaintenanceScheduleReviewScreen Empty state should show no items found message for empty extraction", + "state": "passed" + }, + { + "name": "should create selected schedules on button click", + "fullName": "MaintenanceScheduleReviewScreen Schedule creation should create selected schedules on button click", + "state": "passed" + }, + { + "name": "should only create selected items", + "fullName": "MaintenanceScheduleReviewScreen Schedule creation should only create selected items", + "state": "passed" + }, + { + "name": "should show error on creation failure", + "fullName": "MaintenanceScheduleReviewScreen Schedule creation should show error on creation failure", + "state": "passed" + }, + { + "name": "should update item data via inline editing", + "fullName": "MaintenanceScheduleReviewScreen Editing should update item data via inline editing", + "state": "passed" + }, + { + "name": "should disable create button when selected item has empty subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should disable create button when selected item has empty subtypes", + "state": "passed" + }, + { + "name": "should enable create button after deselecting item with empty subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should enable create button after deselecting item with empty subtypes", + "state": "passed" + }, + { + "name": "should show warning alert for items missing subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should show warning alert for items missing subtypes", + "state": "passed" + }, + { + "name": "should hide warning alert after deselecting items with empty subtypes", + "fullName": "MaintenanceScheduleReviewScreen Subtype validation should hide warning alert after deselecting items with empty subtypes", + "state": "passed" + }, + { + "name": "should disable create button when selected item has no intervals", + "fullName": "MaintenanceScheduleReviewScreen Interval validation should disable create button when selected item has no intervals", + "state": "passed" + }, + { + "name": "should enable create button after deselecting item with missing intervals", + "fullName": "MaintenanceScheduleReviewScreen Interval validation should enable create button after deselecting item with missing intervals", + "state": "passed" + }, + { + "name": "should show warning alert for items missing intervals", + "fullName": "MaintenanceScheduleReviewScreen Interval validation should show warning alert for items missing intervals", + "state": "passed" + }, + { + "name": "should enable create button after editing interval on item", + "fullName": "MaintenanceScheduleReviewScreen Interval validation should enable create button after editing interval on item", + "state": "passed" + }, + { + "name": "should render in fullscreen mode on mobile viewports", + "fullName": "MaintenanceScheduleReviewScreen Responsive layout should render in fullscreen mode on mobile viewports", + "state": "passed" + }, + { + "name": "should render as modal dialog on desktop viewports", + "fullName": "MaintenanceScheduleReviewScreen Responsive layout should render as modal dialog on desktop viewports", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/components/ErrorState.test.tsx", + "tests": [ + { + "name": "should render error message", + "fullName": "ErrorState should render error message", + "state": "passed" + }, + { + "name": "should render retry button when onRetry provided", + "fullName": "ErrorState should render retry button when onRetry provided", + "state": "passed" + }, + { + "name": "should not render retry button when onRetry not provided", + "fullName": "ErrorState should not render retry button when onRetry not provided", + "state": "passed" + }, + { + "name": "should show default message when error has no message", + "fullName": "ErrorState should show default message when error has no message", + "state": "passed" + } + ] + }, + { + "moduleId": "/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx", + "tests": [ + { + "name": "should show loading state", + "fullName": "AdminUsersPage should show loading state", + "state": "failed", + "errors": [ + { + "message": "Error: No QueryClient set, use QueryClientProvider to set one\n at useQueryClient (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/QueryClientProvider.tsx:18:11)\n at useBaseQuery (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/useBaseQuery.ts:54:18)\n at useQuery (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/useQuery.ts:51:10)\n at useUsers (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/hooks/useUsers.ts:47:18)\n at AdminUsersPage (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/pages/admin/AdminUsersPage.tsx:139:46)\n at Object.react_stack_bottom_frame (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20)\n at renderWithHooks (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22)\n at updateFunctionComponent (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19)\n at beginWork (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18)\n at runWithFiberInDEV (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)\n at performUnitOfWork (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22)\n at workLoopSync (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41)\n at renderRootSync (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17450:11)\n at performWorkOnRoot (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:16583:35)\n at performWorkOnRootViaSchedulerTask (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:18957:7)\n at flushActQueue (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react/cjs/react.development.js:590:34)\n at Object..process.env.NODE_ENV.exports.act (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react/cjs/react.development.js:884:10)\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/act-compat.js:46:25\n at renderRoot (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/pure.js:189:26)\n at render (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/pure.js:291:10)\n at renderWithRouter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx:16:16)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx:28:5)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + }, + { + "name": "should redirect non-admin users", + "fullName": "AdminUsersPage should redirect non-admin users", + "state": "failed", + "errors": [ + { + "message": "Error: No QueryClient set, use QueryClientProvider to set one\n at useQueryClient (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/QueryClientProvider.tsx:18:11)\n at useBaseQuery (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/useBaseQuery.ts:54:18)\n at useQuery (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/useQuery.ts:51:10)\n at useUsers (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/hooks/useUsers.ts:47:18)\n at AdminUsersPage (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/pages/admin/AdminUsersPage.tsx:139:46)\n at Object.react_stack_bottom_frame (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20)\n at renderWithHooks (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22)\n at updateFunctionComponent (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19)\n at beginWork (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18)\n at runWithFiberInDEV (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)\n at performUnitOfWork (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22)\n at workLoopSync (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41)\n at renderRootSync (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17450:11)\n at performWorkOnRoot (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:16583:35)\n at performWorkOnRootViaSchedulerTask (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:18957:7)\n at flushActQueue (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react/cjs/react.development.js:590:34)\n at Object..process.env.NODE_ENV.exports.act (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react/cjs/react.development.js:884:10)\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/act-compat.js:46:25\n at renderRoot (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/pure.js:189:26)\n at render (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/pure.js:291:10)\n at renderWithRouter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx:16:16)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx:41:5)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + }, + { + "name": "should render page for admin users", + "fullName": "AdminUsersPage should render page for admin users", + "state": "failed", + "errors": [ + { + "message": "Error: No QueryClient set, use QueryClientProvider to set one\n at useQueryClient (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/QueryClientProvider.tsx:18:11)\n at useBaseQuery (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/useBaseQuery.ts:54:18)\n at useQuery (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@tanstack/react-query/src/useQuery.ts:51:10)\n at useUsers (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/hooks/useUsers.ts:47:18)\n at AdminUsersPage (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/pages/admin/AdminUsersPage.tsx:139:46)\n at Object.react_stack_bottom_frame (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20)\n at renderWithHooks (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22)\n at updateFunctionComponent (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19)\n at beginWork (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18)\n at runWithFiberInDEV (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)\n at performUnitOfWork (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22)\n at workLoopSync (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41)\n at renderRootSync (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:17450:11)\n at performWorkOnRoot (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:16583:35)\n at performWorkOnRootViaSchedulerTask (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:18957:7)\n at flushActQueue (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react/cjs/react.development.js:590:34)\n at Object..process.env.NODE_ENV.exports.act (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/react/cjs/react.development.js:884:10)\n at /Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/act-compat.js:46:25\n at renderRoot (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/pure.js:189:26)\n at render (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/@testing-library/react/dist/pure.js:291:10)\n at renderWithRouter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx:16:16)\n at Object. (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx:63:5)\n at Promise.then.completed (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:298:28)\n at new Promise ()\n at callAsyncCircusFn (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/utils.js:231:10)\n at _callCircusTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:316:40)\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at _runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:252:3)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:126:9)\n at _runTestsForDescribeBlock (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:121:9)\n at run (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/run.js:71:3)\n at runAndTransformResultsToJestFormat (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)\n at jestAdapter (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)\n at runTestInternal (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:367:16)\n at runTest (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/runTest.js:444:34)\n at Object.worker (/Users/egullickson/Documents/Technology/coding/motovaultpro/frontend/node_modules/jest-runner/build/testWorker.js:106:12)" + } + ] + } + ] } ], "unhandledErrors": [], - "reason": "passed" + "reason": "failed" } \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf index ee13d06..f7d4407 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -16,10 +16,14 @@ http { root /usr/share/nginx/html; index index.html; + + # Prevent nginx from including internal port in redirects + # (Traefik handles external-facing port 443) + absolute_redirect off; # Handle client-side routing location / { - try_files $uri $uri/ /index.html; + try_files $uri /index.html; } # Enable gzip compression diff --git a/frontend/public/docs/v2026-01-03.pdf b/frontend/public/docs/v2026-01-03.pdf deleted file mode 100644 index c07d4be..0000000 --- a/frontend/public/docs/v2026-01-03.pdf +++ /dev/null @@ -1,150 +0,0 @@ -%PDF-1.4 -%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com -1 0 obj -<< -/F1 2 0 R /F2 3 0 R ->> -endobj -2 0 obj -<< -/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font ->> -endobj -3 0 obj -<< -/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font ->> -endobj -4 0 obj -<< -/Contents 12 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources << -/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] ->> /Rotate 0 /Trans << - ->> - /Type /Page ->> -endobj -5 0 obj -<< -/Contents 13 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources << -/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] ->> /Rotate 0 /Trans << - ->> - /Type /Page ->> -endobj -6 0 obj -<< -/Contents 14 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources << -/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] ->> /Rotate 0 /Trans << - ->> - /Type /Page ->> -endobj -7 0 obj -<< -/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources << -/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] ->> /Rotate 0 /Trans << - ->> - /Type /Page ->> -endobj -8 0 obj -<< -/Contents 16 0 R /MediaBox [ 0 0 612 792 ] /Parent 11 0 R /Resources << -/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] ->> /Rotate 0 /Trans << - ->> - /Type /Page ->> -endobj -9 0 obj -<< -/PageMode /UseNone /Pages 11 0 R /Type /Catalog ->> -endobj -10 0 obj -<< -/Author (FB Technologies LLC) /CreationDate (D:20260103171454+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260103171454+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) - /Subject (\(unspecified\)) /Title (MotoVaultPro Terms of Service) /Trapped /False ->> -endobj -11 0 obj -<< -/Count 5 /Kids [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R ] /Type /Pages ->> -endobj -12 0 obj -<< -/Filter [ /ASCII85Decode /FlateDecode ] /Length 2145 ->> -stream -GatU3gN)%,&:O:Sm-%%*.'uO&(\K@Ti_2CNa,SV]3g1A9"=5<9_#gDQjm0MdOHO;#U>%^F=VS*m4m21Jb(i"Vna6?Z$#m;(bA/3t!*09u$Z?'H)gK-/q"pf_A8+8qHYN5*(YTsUr2M]__sbI_^)'$I"Ve.nl*%*aJ$N*5[p$V*ldC;5i/[,MOmuMWq?j9qD"':$T*-4MIen$B7IY7nFb(>Xk#4S\;-r[]KY)<[]:FlTBVTuCqu0Zr;%RtT0!mQ%pi%K@;^$<2R\48jk5/^lceB+W:>\GNcH7EPC4'_FmiDjY7sO#k<`q9+@e\UGNCo-fUD'c\fh1Bf.4PUp-=morfCXP,h^mJ71SaL7@sC*U6>S"$mJMOi%C_.R13<>eYb'W&SpnWg51'[7+C(E:(^-c\'i-89`'ZUnN,^J)04K.9pLQ1O^D+hLgH7u@'X*]tMdVXotTQnOL,]>Z.\.<)+r5O!k!bLIEG,1Gn=jQG*N1slEc=+5dlAfA/B8C)eA,D^`*-*Vk@.M+/`6,<.M)&lhY4k+h@o?I:Y%48An6ae#6gibGMblV^fcWfb-JH`eegmfSR.(NZ1AEQQg;`B\&&rDj9=Rt+f-=W>#;_6ASm:mn5mk-5s"?"'.cN=)Ng$LNPj]-DF$'O'0g^B)Z`PCOFdO&L*//VDt8PR4O8a94X$sSNe#%bIt;_H_.`mr0aD#),ie*BGYN;`g6uk_pEKaQK+Bq11)*N&J3u^8]>%$.Q,tp3TcD78Iae&T)+/,3J&ge9FAn=a'n;l&:.(31`aEGGCe$EaN`+d)**"PKTi]EmX*C\hmISqa#_X,S9htNAXLuA%Drnj0%X`UdK'gaB`gr/Ob%I&KpYO]tcY576YOXRc9^0lCb#I2#G]56[nBIG2B%PO;bd6cngO@)?Z`.d*:!8;du(2IbE9kc18-Bnbpr#oNgLRo/'"uEI;a#BH-X@P8!@5?k0DlPrIWdW5r>m&5]3j4,mC5I&6nhd(.bgC8eZY$E*hpdd3Tg$74"?G>FGU/5g7i-p%aF9P?WY5d;"!+cgNXmOr1`\uj07c!I^sff<1Oh:4=`.^6A]W,:l@?oWR!i;%h+aY>,3_Qer8PZDQ\HArdZXJ-endstream -endobj -13 0 obj -<< -/Filter [ /ASCII85Decode /FlateDecode ] /Length 2354 ->> -stream -Gb!#\?#Sc3&q/*0)"bq\E3tr/+hN.p\(RK@G+0i8('X9G8@6WG8Q8:(Nr/-hL8&0>dDeH[m(G+bbPE>ocC?mNUR28\`;Y6e&q*!BG9::O':LR&*^qA`O4g]0=$_&64W^179b&6:5PVd=jm"fsU*Q;WE9HSI=@u0q]_VeF4opkaMTKrU9`T[$Er6W73/*]$+rVi(F?6,k"sIDbhL.rIYV@(Vn/o!4h@LWTdW?uU9pOJSn*#BAmd*e1[=>]1H$rC/Y-.3Vo(Ir)rVYp>p?KQY:Hr*5Isol;][PqBP"P8Cb,-H[k[`eR7KA]p18fmZ'k.j(DbiY=YgL-h;l+K/d;,"?19H8+%:r#kb#qp+\/*(5;:'qF$<8['?);VsVl"2[i5cRQ-#a3:?a8']L2K@cGOOiLN1+TP,>#'.$*Wk_5:Kts/m;kZjk$;WbAFP6R']q"2M![%Lu[Rn%f),+FpKF-nBKQjnoNS*HL0=4?>ecN[E4hiitYRKri`\^hd7c)!4G.6t*jjh.ZsjEQSTh$q$hu^t#F$D^24Gnfi/,>^Gjn.JbYjh++3L0^IA0FcmNpF!!KuBM/"J$g&7CV62,R'8_i_oU1TW'++UgBfJ5>6c;pohYE[/J&!_ImTh"2k4"+DW0F$^*e.05&uEpb4uqh7TB[/KRUpoiRM**id)\n\RZG6?"R-bL>h+SjD(Sg=6R6`_mN^od$3=g;iT.Dk"!lGfeS,0/Xc..`X$__'&Yi4WSU]rn0M[>O3phR,\WrIO`+&HQsJ6rI*@JfW\o5D]bQ7U?7_&"c`0Gi8OJG``SQiE[NMee3N!=FuTW,4cJE@KL>S$6c`9HV$^g+<$^/bX@g4c#W$rOCSDolN-].f-hA#5@P+l;h.V&E4V?T1lubIP`pmS%Hp`][?GPb[K@+/;mFu`2NC(80Elhne6s.;&Qg(e.,]NHWGL:RI5sLA1jL]]B*IbYk^DgGl4c&Hc#5nS\63/eZ,n^VK"jQfaM>J.L%MWI@I$Fb7:2D^[?f*2P$&;WkU$-beQQWn9GGeZJ[)XH=R$KTOIXAq^GhbT9g$ue*f7\A(#Q6h];nk0?m4o]4I*m*WA'LNiTC"A63JOKW@^Ba*5#fporZIPdW6]MP"\1WS(?o[4\i-_9`8K%nG\*:-c$>S[WM($D1C?VS:G(RS;I.!K,-krCjj"rNG>G:[l4P,Vn2bSD8WDCRWrt"e*@(6r>e5[BP!JrRJ>XB2FO>hB(]KQ_Jm[hbg*h&57u_aG1Jc:[@_c.4Wi\cnpE<@tt6JU`8LYHXh;r`2M,BS5Fe3?L,k]-f'@2an`^BrcjPXpS77h((X:raL;enDL'*&W_,nDEu:EJ7EFG9nBj]lnM)*%J"XXRJ9.7M]>;*hi'*>(4YFF##luOH](@<=s,nn!/\qsSY\S.hHsQ;R)-ZKZ``e5-J?=71k;3iSj5&hA:HVRV"o!?;kAOp)4R3n6^7J"LN8fDR*Ut@U,78>%&7r?4-Hu3=#D5=sZ#]F1hf"g63;n?,E(jumr=A[o.l;\Dg2cZ5[d3iH!tKL\pnX[[iBM$Z->6QEIPSG'P>Hc1H>TkQLs&#M!!o_dlr.qUmicnS]9$1%lm(a5Yq2a1HLC&Be+S_OPqgF(u78H[j=Zq^B,8$Uq)'WdUKUi8]QYml5!,O1$-"_s(]r%/p/55GCaBe-a!5mc"trkWi>Hft5CYJ?E\usq`=aPRLKelpLM)2DOJfL#5D\>e,~>endstream -endobj -14 0 obj -<< -/Filter [ /ASCII85Decode /FlateDecode ] /Length 2256 ->> -stream -GauHLgN);`(4FM1m-6VBR47-'S*R^Ii,lTR>`N[LPH)8$,D^Z)`Rk6\^HY?m&kJ4rVbbj)<"N0cQaPGZ-oTWGA"16tP3=NlgO#E+LQX[N%BDcd8:GUPpXS4of0$!9S\kQQ2(s:Q?7b^Tq]NUa>7S'1Ng@/Ml[`%+JZU^c/gMaU=I(5q'_>cY;Z7U]h^JjkrJ[(POk3.on#Aj8JutV5>1SMk1WncF@O]*ND9ccNIIX;>/NY>A/<&Wug@A,WV3.u3U]N+0/7iX5?dc'W#iXX&4^!W):Q;WJMqnn`a4c]p(<-a+gtZ9Q>$S&0%Ks*c5KY(SHMlr;.t-6Cl$9nf=i7Tla^(`=)2,Pp08o@g/OIFrECEkeOTi5ZT&Zr2$dh+RS33\O&[bOsaX&/&.SGQ?^D7;Tlg##*hhsK91oeAS*Ot4r`>+?bWJ0)L+@`aW"HL$N&*8rV?A8*OF\Ug,bR)f\g7=H;L'7glH2``$n[V[nWG.#5JG'J%iNaMV=>n`EiAs>?pj4,BF6Xm23)ar2$2?e#r4;3*P2UP"8N+iV:rgZ&sW!Y$]@PMgb>o,ipW"A&tL;4@*nA!SLpt9E6BRZJP2g*EaE)#.F%+7!m8c&6RU1lknDGO:FXPm+#HjMZ)`^kkY(j3#Xp_ID8RQ+aHHOAAil4Z>\;IU->,:^p2[@Vq!W?\F\]&[O<"2d0&j`?n`I[s(C=%',pbq+@m-d^O)d'qc]c2-lG0mP;%Q"l3D;^oR[)XL#JGSL=P_%XcCNQl`XV/H,Cosiu/&],4`9ERNEIDJtQ/dR*jYt"IR1=Y(IZ/7WUdFaA?TJm1jLrPWl4Gs(UH+i&[np39o&K$O_2o4uIJn0?2G:G$$][ILSiH-ZY7)u:.5$c6Ui0]O$#\,dUb&E_oEk0eg;=p)^A.XuW);.#UB`Hj1@b[dYjY(aC()9Q_>b/?6N8D'6ViKGmj0$#O\`Y&,2st62SKK0_f"._@/<_WMJhi,hm_fh1bf6q;&Yt`!`cp%:Xj5KJ)oKIP(aW4h([_ara:K2Bf98;hKP3,0$K\B@nYV7p4K)?S&s)`/6RQiU'ebliT1"ihTnAk%&(+1tE9:Z2+1l+K&CDp."DBjp0'!j_j!VeZeLWb&K4mein!]N._M[&u4A@;sTj!8%q5_/fq^_=%9;U8+W"+ZA%FR/^c#4.'m?1F.4V^4Y/>f_'Pj+CBoqIp*"bM.-6"`klb>\2Vf/)RNV4aXs@%:^g*10@^BQ/L>jcm_Ce?PoK^CY@=OI-<)t23tD)TgATaer_^)1c+boDI$oo'82"7Tkj!X`I/:n~>endstream -endobj -15 0 obj -<< -/Filter [ /ASCII85Decode /FlateDecode ] /Length 2720 ->> -stream -GatU4>BALj&q9"FFQJ@L+pto439Oja/3?g>irqFl=8>9#K:,+DN\2[FYMZNROtEpa\B`f.kV=qWIJ^bHO\S:AlfpEfM(X2PI&3$EOM;spdOatPjS?L(HhGHJ%1G+l*mdUR8&s6JR9/U[plV]s1q!jZ(K:>\/^ggg)&7?u9"rC+SC.kK7.qflnWEbgB0fSAWdhpUYDsJfM\1%K_>SUC<:@F3LLf'l(U@"RL4%=`@C:2o33i)^_R6<@6X!CWS-_JM8hsP-30B]JJC\0SP4a?WA51f8_G?94OiVV/"G2D?JPb\HOlZ[uA:njjh-+D7/5k7]AdirP6Q>KS,_IH^a[3:dFV&$l^J4<)<)Pb]CI;`FR810]c^dT\M]VaN$);MmP@r$+OJb\>'F>*RBTe0=rb*,QD4onK7g_#>o=VF]3;:i0/#&[JC\[t`Jc-[o6T`_6][P>%+[2m;l'Oi(*"5Slrdi,&@YN_EXZfZNuWVS*1L#0OZWZh:p]l!5t&*4DJW(H.8H5ms3J[07ujo4)kF&sD74nk-;0=)Cq]W","gsX\XH<9k4q1D-mTA6It]O0((idOI?n(O>]Ed%E#?_,:XgGYNLFVW=TNCK?k0uaREPsT=&RnVj)dWCp(Rl$%UW0Knq-*t7_FD18FT.U6n$P3rdNALb$Jp,9Wg8r9C!TjCI!8B-cmIA79.ACT.erRaj&H+*`OhEH2mBT\!e+eS7SD"g.O_[bp4_7N-BV3BJ?j/p[@4$1MG_5N:W:2Qk#!MI54>!27L"<(#+60sUA+$))N$>,=n6s-KAaH*,d?,knpI,>(%@KQphB>)s5KfIbN*fL'[#$SZh@Vo'>kW=LNahr*pkr(Eo6uDqAct-UU0q8b/X-JPpY`@s;e2%4nHfi>*#;'O?8:fd=Or=?'sE]cuO5FH7u>,=)dm5dsng&NE%qFs/FM2D$90K1`6crN0%NS_%S3B_e(fHRR>I,&ITNaC9'=DnF`1=B2]E87I$Sa5?QAIZBm'VrCLW^6Q7*R/#@r69'8Qeo,"]`1V(AWS<-4#"Jm(@r%H?4NQ/^[o!.QRNI@)7QNd-;F6(P*%7:/P'oOF<@m@$ERL)".5AqBUU)41ppAJ16H2U1<4+=mdcmXf5=e^SRkEbEa0C$9b_rB5,*BqC&tCi[KPiFE+7O.fJT5>Z?o+QQ]mhdd5B"A]NkIcV&IUMB44?LG>ednnMKP;+Sr5<=R"N1G!tlt%e+C0*8<:8^Xd9<_>6dE$d>XQb7YgGBM"aQr-FNDX"G+\Xno/+'U.kgC?d0mf[X?\ZCA4#;#r?r(M;)2;H>:!Q.8FK4:_'a6F!jPF4kc,*h8FmnMmh/YgLp!WHYY.6`AmdpnC\.<\dSD@N#FRIFl`"kjG>9SNED<%r1^j"ZjF6\TA)H\,PaZMAAkACXq65drj-eX)m6ZGB3fL9(52*G/a"+;d04]LuAWD1#%%)EUDo:YQkMi,cnWIH8UG,Ip:!dI7H3GGqn(LiFG7a$=Nu(q?!l+b2Xf3$[MJFE7_<%Y\9t5F3\mkq$Pa$3SNBclB_D9uc)r-sI]i&$0LbPqC;M-I$Au7W;Mb59'*0Y'B+gkH-e0k.#,/b!^4;(^_V;!A@65N0A,!CRK%ATeo'e=YnoS5>e'(P=*sX.KZ6',#cN;/ocol(/L?5N8I(f"-j]TQ-Viq+9/Y\YDFHFXZA$dFn9!jAfJqkrAONIf:QJfP\Y'i!9(=9%DZo&boSpSr:Q7?_779JMo5:ff\Gq-38%j.RW(4L@['N\2_u@+TV7L[Ld.)ZWaDrS[JNr)i_Y"7_%t"^nV;S]Zd`]u>B'I])k]_DQWAiko#pJ\*o$]O%QFPjhh8]M76os0n]m7(m:1eF-<8,h'fNd@%,!css1pp59dNu!Yd.oW,E/Y_mpKaMo]e@?9I+k2$IPHQ!Dr:mdAYqH9Q;1"endstream -endobj -16 0 obj -<< -/Filter [ /ASCII85Decode /FlateDecode ] /Length 1302 ->> -stream -GatU2gMYb8&:N/3bbJDk[!7k+V^=]7'X1fN&J>69bBc%?2I5`#bG>@7lT;GK>Z$e],EW"",.VbTkKFg_0VuNDr0J/6qZU52`t0W&=@&HG)(][`j'qY/eD7YB2l>!8]+Lmu"fLglj:cML^5/NeApM8`.N%&HDa0.Lq`AG!JZc#hY2Bu)Xcdo0Mf5ip,m9hVoU$dfn%Z<)3[Cc?6PP1jjW6G]tnnNXgdsI.!n9c''DFAlk'8R_9@\6@RG7?\IPd0Jfk]RRRIs:Y2N'H9^C';X0Q7"_OY0RCV:V`Z8Y6J\gR.cZhWYYZF7=i<(41u`5ZeGIfXagD2$aU,Y/sio_X['`fhgro,&7TY3f^A%MchTc\<"8>'&$@J1sW0C1Lmmd+-\Q%EbcRX$jJ$C04psSU)CqPQ=Ft]5$\Or(KjiRt]RMuhk>lZ#?R\"&Y&7E"nr`_u$Dbn8aO.#eH[cKA7n6ubL,J$;r'(tFpW&*1h;U`RjRds;YOk0oP.'BDiGUhj^,/)$[bdQ,hE34s70K=YWt1q4W;<(7N4;VK.c#s9W&47Orp1.)3K!@C5H+%K=2ZU-Ca12VslrCFB,,NN8)^[J(-dLi"l`>Z74a.>%+6$V!:j9TSihCW6fc^,cdJ2(b?&6@Xb\$0?\IR*#ZqsubM021V9Eug]VeQ,;JS+DSY#T2-PNLJ75""#^8Z(ECXZPXr1q'*9c@s=T0H>B7G[Hq.)BCQgP2]"89SKW;a2UNE;CfI=+e#M`Ki.l-p:GHD$QU!$,sac&[aX7$7BgI9Am(8/#-T#L"08oj)\UQ]+J\XE%Kg0DZ"eiC5atN,IEeU4)PQ9EZpNFYCA_"ADe\ot*`2@7Un`@V#VPSI*pPYQrIi6"g1&&0GubC+mtjTG2HJRq^$4\A"%ZkQe#?ri-BAc\\qendstream -endobj -xref -0 17 -0000000000 65535 f -0000000073 00000 n -0000000114 00000 n -0000000221 00000 n -0000000333 00000 n -0000000528 00000 n -0000000723 00000 n -0000000918 00000 n -0000001113 00000 n -0000001308 00000 n -0000001377 00000 n -0000001683 00000 n -0000001767 00000 n -0000004004 00000 n -0000006450 00000 n -0000008798 00000 n -0000011610 00000 n -trailer -<< -/ID -[<2ea7118942454ce9d6e3514eaf0def7b><2ea7118942454ce9d6e3514eaf0def7b>] -% ReportLab generated PDF document -- digest (http://www.reportlab.com) - -/Info 10 0 R -/Root 9 0 R -/Size 17 ->> -startxref -13004 -%%EOF diff --git a/frontend/public/guide/ maintenance-records-desktop.png b/frontend/public/guide/ maintenance-records-desktop.png new file mode 100644 index 0000000..3a6365a Binary files /dev/null and b/frontend/public/guide/ maintenance-records-desktop.png differ diff --git a/frontend/public/guide/add-vehicle-form-desktop.png b/frontend/public/guide/add-vehicle-form-desktop.png new file mode 100644 index 0000000..77324cc Binary files /dev/null and b/frontend/public/guide/add-vehicle-form-desktop.png differ diff --git a/frontend/public/guide/dashboard-desktop.png b/frontend/public/guide/dashboard-desktop.png new file mode 100644 index 0000000..d7fd84b Binary files /dev/null and b/frontend/public/guide/dashboard-desktop.png differ diff --git a/frontend/public/guide/documents-desktop.png b/frontend/public/guide/documents-desktop.png new file mode 100644 index 0000000..57332cc Binary files /dev/null and b/frontend/public/guide/documents-desktop.png differ diff --git a/frontend/public/guide/fuel-logs-desktop.png b/frontend/public/guide/fuel-logs-desktop.png new file mode 100644 index 0000000..5e2d49b Binary files /dev/null and b/frontend/public/guide/fuel-logs-desktop.png differ diff --git a/frontend/public/guide/gas-stations-desktop.png b/frontend/public/guide/gas-stations-desktop.png new file mode 100644 index 0000000..fb12f3e Binary files /dev/null and b/frontend/public/guide/gas-stations-desktop.png differ diff --git a/frontend/public/guide/log-fuel-modal-desktop.png b/frontend/public/guide/log-fuel-modal-desktop.png new file mode 100644 index 0000000..5cc353f Binary files /dev/null and b/frontend/public/guide/log-fuel-modal-desktop.png differ diff --git a/frontend/public/guide/login-desktop.png b/frontend/public/guide/login-desktop.png new file mode 100644 index 0000000..a36b8a3 Binary files /dev/null and b/frontend/public/guide/login-desktop.png differ diff --git a/frontend/public/guide/maintenance-schedules-desktop.png b/frontend/public/guide/maintenance-schedules-desktop.png new file mode 100644 index 0000000..669f6db Binary files /dev/null and b/frontend/public/guide/maintenance-schedules-desktop.png differ diff --git a/frontend/public/guide/onboarding-desktop.png b/frontend/public/guide/onboarding-desktop.png new file mode 100644 index 0000000..ed32929 Binary files /dev/null and b/frontend/public/guide/onboarding-desktop.png differ diff --git a/frontend/public/guide/settings-desktop.png b/frontend/public/guide/settings-desktop.png new file mode 100644 index 0000000..336f1fb Binary files /dev/null and b/frontend/public/guide/settings-desktop.png differ diff --git a/frontend/public/guide/vehicle-detail-desktop.png b/frontend/public/guide/vehicle-detail-desktop.png new file mode 100644 index 0000000..d9a7e7b Binary files /dev/null and b/frontend/public/guide/vehicle-detail-desktop.png differ diff --git a/frontend/public/guide/vin-decode-desktop.png b/frontend/public/guide/vin-decode-desktop.png new file mode 100644 index 0000000..9269204 Binary files /dev/null and b/frontend/public/guide/vin-decode-desktop.png differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fbbd914..497c2ba 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -47,6 +47,9 @@ const AdminLogsMobileScreen = lazy(() => import('./features/admin/mobile/AdminLo const AdminCommunityStationsPage = lazy(() => import('./features/admin/pages/AdminCommunityStationsPage').then(m => ({ default: m.AdminCommunityStationsPage }))); const AdminCommunityStationsMobileScreen = lazy(() => import('./features/admin/mobile/AdminCommunityStationsMobileScreen').then(m => ({ default: m.AdminCommunityStationsMobileScreen }))); +// Public pages (lazy-loaded) +const GuidePage = lazy(() => import('./pages/GuidePage/GuidePage').then(m => ({ default: m.GuidePage }))); + // Auth pages (lazy-loaded) const SignupPage = lazy(() => import('./features/auth/pages/SignupPage').then(m => ({ default: m.SignupPage }))); const VerifyEmailPage = lazy(() => import('./features/auth/pages/VerifyEmailPage').then(m => ({ default: m.VerifyEmailPage }))); @@ -368,7 +371,7 @@ function App() { // Skip on auth routes -- their query params must survive until Auth0 SDK processes them useEffect(() => { const path = window.location.pathname; - if (path === '/callback' || path === '/signup' || path === '/verify-email') return; + if (path === '/callback' || path === '/signup' || path === '/verify-email' || path === '/guide' || path === '/guide/') return; const screen = routeToScreen[path]; if (screen && screen !== activeScreen) { navigateToScreen(screen, { source: 'url-sync' }); @@ -380,7 +383,7 @@ function App() { // Auth0 SDK needs for handleRedirectCallback (child effects fire before parent effects) useEffect(() => { const path = window.location.pathname; - if (path === '/callback' || path === '/signup' || path === '/verify-email') return; + if (path === '/callback' || path === '/signup' || path === '/verify-email' || path === '/guide' || path === '/guide/') return; const targetPath = screenToRoute[activeScreen]; if (targetPath && path !== targetPath) { window.history.replaceState(null, '', targetPath); @@ -499,8 +502,9 @@ function App() { const isSignupRoute = location.pathname === '/signup'; const isVerifyEmailRoute = location.pathname === '/verify-email'; const isOnboardingRoute = location.pathname === '/onboarding'; + const isGuideRoute = location.pathname === '/guide' || location.pathname === '/guide/'; const isAuthRoute = isSignupRoute || isVerifyEmailRoute || isOnboardingRoute; - const shouldShowHomePage = !isGarageRoute && !isCallbackRoute && !isAuthRoute; + const shouldShowHomePage = !isGarageRoute && !isCallbackRoute && !isAuthRoute && !isGuideRoute; const [callbackTimedOut, setCallbackTimedOut] = useState(false); useEffect(() => { @@ -635,6 +639,21 @@ function App() { ); } + if (isGuideRoute) { + return ( + + +
Loading guide...
+ + }> + +
+ +
+ ); + } + // Signup route is public - no authentication required if (isSignupRoute) { return ( diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 46e49f4..566d749 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -14,6 +14,7 @@ import BuildRoundedIcon from '@mui/icons-material/BuildRounded'; import PlaceRoundedIcon from '@mui/icons-material/PlaceRounded'; import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded'; import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded'; +import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded'; import MenuIcon from '@mui/icons-material/Menu'; import ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded'; import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded'; @@ -51,6 +52,7 @@ export const Layout: React.FC = ({ children, mobileMode = false }) { name: 'Gas Stations', href: '/garage/stations', icon: }, { name: 'Documents', href: '/garage/documents', icon: }, { name: 'Settings', href: '/garage/settings', icon: }, + { name: 'Guide', href: '/guide', icon: }, ]; const sidebarWidth = sidebarCollapsed ? 64 : 256; diff --git a/frontend/src/pages/CLAUDE.md b/frontend/src/pages/CLAUDE.md new file mode 100644 index 0000000..8f26542 --- /dev/null +++ b/frontend/src/pages/CLAUDE.md @@ -0,0 +1,20 @@ +# frontend/src/pages/ + +Page-level components and routing targets. + +## Files + +| File | What | When to read | +| ---- | ---- | ------------ | +| `HomePage.tsx` | Public landing page (Home, Features, About, Guide, Sign Up, Login) | Modifying public site | +| `SettingsPage.tsx` | Settings page component | User settings | +| `SecuritySettingsPage.tsx` | Security settings page | Security/privacy settings | + +## Subdirectories + +| Directory | What | When to read | +| --------- | ---- | ------------ | +| `GuidePage/` | Public user guide at `/guide` (10 sections, TOC, screenshots) | Guide content or structure | +| `HomePage/` | HomePage sub-components (HeroCarousel, FeaturesGrid) | Landing page features | +| `admin/` | Admin-only pages | Admin functionality | +| `__tests__/` | Page-level integration tests | Testing pages | diff --git a/frontend/src/pages/GuidePage/CLAUDE.md b/frontend/src/pages/GuidePage/CLAUDE.md new file mode 100644 index 0000000..897bf76 --- /dev/null +++ b/frontend/src/pages/GuidePage/CLAUDE.md @@ -0,0 +1,34 @@ +# frontend/src/pages/GuidePage/ + +Public-facing user guide page at `/guide`. Renders `docs/USER-GUIDE.md` content as styled React components with screenshots. + +## Files + +| File | What | When to read | +| ---- | ---- | ------------ | +| `GuidePage.tsx` | Main page component with layout, nav bar, and section rendering | Modifying page structure or navigation | +| `GuideTableOfContents.tsx` | Sticky sidebar TOC (desktop) / collapsible accordion TOC (mobile) | Modifying TOC behavior or styling | +| `guideTypes.ts` | Section metadata (id, title, subSections) used by TOC and anchors | Adding/renaming sections | + +## Subdirectories + +| Directory | What | When to read | +| --------- | ---- | ------------ | +| `components/` | Shared components (GuideScreenshot, GuideTable) | Modifying screenshot or table rendering | +| `sections/` | 10 content section components (one per guide chapter) | Updating guide content | + +## Architecture + +- Static JSX components (no react-markdown dependency); content changes require editing section files directly +- Each section is 150-250 lines of JSX; individual files prevent a 2000+ line monolith +- GuideScreenshot provides consistent lazy loading (`loading="lazy"`), responsive sizing, and WebP support across all sections +- GuideTable renders MUI Table from `headers[]` and `rows[][]` arrays, preventing verbose MUI Table boilerplate in each section +- The page has its own public nav bar (matching HomePage style) because it is accessible without authentication +- `/guide` route is detected in App.tsx and rendered outside the authenticated `/garage/*` shell + +## Key Patterns + +- Section anchor IDs must match `guideTypes.ts` section IDs for TOC smooth scroll to work +- Screenshots stored in `frontend/public/guide/` as static assets with `loading="lazy"` +- TOC uses `window.innerWidth < 768` breakpoint: sidebar on desktop, MUI Accordion on mobile +- Guide link in HamburgerDrawer uses `window.location.href` (not Zustand navigation) because `/guide` is outside the `/garage/*` shell diff --git a/frontend/src/pages/GuidePage/GuidePage.tsx b/frontend/src/pages/GuidePage/GuidePage.tsx new file mode 100644 index 0000000..f8cae6d --- /dev/null +++ b/frontend/src/pages/GuidePage/GuidePage.tsx @@ -0,0 +1,257 @@ +import { useState, useEffect, useCallback, Suspense } from 'react'; +import { useAuth0 } from '@auth0/auth0-react'; +import { useNavigate } from 'react-router-dom'; +import { GuideTableOfContents } from './GuideTableOfContents'; +import { guideSections } from './guideTypes'; +import { + GettingStartedSection, + DashboardSection, + VehiclesSection, + FuelLogsSection, + MaintenanceSection, + GasStationsSection, + DocumentsSection, + SettingsSection, + SubscriptionSection, + MobileExperienceSection, +} from './sections'; + +export const GuidePage = () => { + const { loginWithRedirect, isAuthenticated } = useAuth0(); + const navigate = useNavigate(); + const [activeSection, setActiveSection] = useState(guideSections[0].id); + const [isScrolled, setIsScrolled] = useState(false); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + const handleAuthAction = useCallback(() => { + if (isAuthenticated) { + navigate('/garage'); + return; + } + loginWithRedirect({ appState: { returnTo: '/garage' } }); + }, [isAuthenticated, navigate, loginWithRedirect]); + + const handleSignup = useCallback(() => { + navigate('/signup'); + }, [navigate]); + + // Scroll to hash target after lazy-loaded sections render + useEffect(() => { + const hash = window.location.hash.slice(1); + if (!hash) return; + + // Poll for the element since Suspense sections load asynchronously + let attempts = 0; + const interval = setInterval(() => { + const target = document.getElementById(hash); + if (target) { + clearInterval(interval); + target.scrollIntoView({ behavior: 'smooth' }); + } + if (++attempts >= 20) clearInterval(interval); + }, 100); + + return () => clearInterval(interval); + }, []); + + // Track scroll position for nav background and active section + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 50); + + // Determine active section from scroll position + const sectionElements = guideSections.map((s) => ({ + id: s.id, + element: document.getElementById(s.id), + })); + + for (let i = sectionElements.length - 1; i >= 0; i--) { + const { id, element } = sectionElements[i]; + if (element) { + const rect = element.getBoundingClientRect(); + if (rect.top <= 120) { + setActiveSection(id); + break; + } + } + } + }; + + window.addEventListener('scroll', handleScroll, { passive: true }); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const sectionFallback = ( +
Loading section...
+ ); + + return ( +
+ {/* Navigation Bar - matches HomePage style */} + + + {/* Page Content */} +
+ {/* Page Header */} +
+

+ User Guide +

+

+ Precision Vehicle Management -- Track every mile. Own every detail. +

+
+ + {/* Layout: TOC sidebar + content */} +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + {/* Footer */} +
+
+

+ © {new Date().getFullYear()} FB Technologies LLC. All rights reserved. +

+
+
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/GuideTableOfContents.tsx b/frontend/src/pages/GuidePage/GuideTableOfContents.tsx new file mode 100644 index 0000000..a2b522c --- /dev/null +++ b/frontend/src/pages/GuidePage/GuideTableOfContents.tsx @@ -0,0 +1,92 @@ +import { useState, useEffect } from 'react'; +import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { guideSections } from './guideTypes'; + +interface GuideTableOfContentsProps { + activeSection: string; +} + +export const GuideTableOfContents = ({ activeSection }: GuideTableOfContentsProps) => { + const [isMobile, setIsMobile] = useState(false); + const [tocExpanded, setTocExpanded] = useState(false); + + useEffect(() => { + const check = () => setIsMobile(window.innerWidth < 768); + check(); + window.addEventListener('resize', check); + return () => window.removeEventListener('resize', check); + }, []); + + const handleClick = (sectionId: string) => { + const element = document.getElementById(sectionId); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + if (isMobile) { + setTocExpanded(false); + } + }; + + const tocContent = ( + + ); + + if (isMobile) { + return ( +
+ setTocExpanded(expanded)} + sx={{ + backgroundColor: 'rgba(255,255,255,0.03)', + border: '1px solid rgba(255,255,255,0.08)', + borderRadius: '8px !important', + '&:before': { display: 'none' }, + }} + > + } + sx={{ + color: 'rgba(242,243,246,0.9)', + fontWeight: 600, + minHeight: 48, + '& .MuiAccordionSummary-content': { margin: '12px 0' }, + }} + > + Table of Contents + + + {tocContent} + + +
+ ); + } + + return ( + + ); +}; diff --git a/frontend/src/pages/GuidePage/components/GuideScreenshot.tsx b/frontend/src/pages/GuidePage/components/GuideScreenshot.tsx new file mode 100644 index 0000000..aa7c5cf --- /dev/null +++ b/frontend/src/pages/GuidePage/components/GuideScreenshot.tsx @@ -0,0 +1,26 @@ +interface GuideScreenshotProps { + src: string; + alt: string; + caption?: string; + mobile?: boolean; +} + +export const GuideScreenshot = ({ src, alt, caption, mobile }: GuideScreenshotProps) => { + return ( +
+
+ {alt} +
+ {caption && ( +
+ {caption} +
+ )} +
+ ); +}; diff --git a/frontend/src/pages/GuidePage/components/GuideTable.tsx b/frontend/src/pages/GuidePage/components/GuideTable.tsx new file mode 100644 index 0000000..9c5efbc --- /dev/null +++ b/frontend/src/pages/GuidePage/components/GuideTable.tsx @@ -0,0 +1,68 @@ +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, +} from '@mui/material'; + +interface GuideTableProps { + headers: string[]; + rows: string[][]; +} + +export const GuideTable = ({ headers, rows }: GuideTableProps) => { + return ( + + + + + {headers.map((header, idx) => ( + + {header} + + ))} + + + + {rows.map((row, rowIdx) => ( + + {row.map((cell, cellIdx) => ( + + {cell} + + ))} + + ))} + +
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/guideTypes.ts b/frontend/src/pages/GuidePage/guideTypes.ts new file mode 100644 index 0000000..272d420 --- /dev/null +++ b/frontend/src/pages/GuidePage/guideTypes.ts @@ -0,0 +1,114 @@ +export interface GuideSubSection { + id: string; + title: string; +} + +export interface GuideSection { + id: string; + title: string; + subSections: GuideSubSection[]; +} + +export const guideSections: GuideSection[] = [ + { + id: 'getting-started', + title: '1. Getting Started', + subSections: [ + { id: 'creating-an-account', title: 'Creating an Account' }, + { id: 'logging-in', title: 'Logging In' }, + { id: 'onboarding', title: 'Onboarding' }, + { id: 'trouble-logging-in', title: 'Trouble Logging In' }, + ], + }, + { + id: 'dashboard', + title: '2. Dashboard', + subSections: [ + { id: 'your-fleet-overview', title: 'Your Fleet Overview' }, + { id: 'quick-actions', title: 'Quick Actions' }, + ], + }, + { + id: 'vehicles', + title: '3. Vehicles', + subSections: [ + { id: 'viewing-your-vehicles', title: 'Viewing Your Vehicles' }, + { id: 'adding-a-vehicle', title: 'Adding a Vehicle' }, + { id: 'vin-decode', title: 'VIN Decode' }, + { id: 'vehicle-detail-page', title: 'Vehicle Detail Page' }, + { id: 'editing-a-vehicle', title: 'Editing a Vehicle' }, + { id: 'deleting-a-vehicle', title: 'Deleting a Vehicle' }, + ], + }, + { + id: 'fuel-logs', + title: '4. Fuel Logs', + subSections: [ + { id: 'fuel-logs-overview', title: 'Fuel Logs Overview' }, + { id: 'logging-fuel', title: 'Logging Fuel' }, + { id: 'receipt-scanning', title: 'Receipt Scanning' }, + { id: 'editing-and-deleting-fuel-logs', title: 'Editing and Deleting Fuel Logs' }, + ], + }, + { + id: 'maintenance', + title: '5. Maintenance', + subSections: [ + { id: 'maintenance-records', title: 'Maintenance Records' }, + { id: 'adding-a-maintenance-record', title: 'Adding a Maintenance Record' }, + { id: 'maintenance-schedules', title: 'Maintenance Schedules' }, + { id: 'creating-a-schedule', title: 'Creating a Schedule' }, + ], + }, + { + id: 'gas-stations', + title: '6. Gas Stations', + subSections: [ + { id: 'finding-stations', title: 'Finding Stations' }, + { id: 'saved-stations', title: 'Saved Stations' }, + { id: 'premium-93-stations', title: 'Premium 93 Stations' }, + ], + }, + { + id: 'documents', + title: '7. Documents', + subSections: [ + { id: 'documents-overview', title: 'Documents Overview' }, + { id: 'adding-a-document', title: 'Adding a Document' }, + { id: 'document-types', title: 'Document Types' }, + ], + }, + { + id: 'settings', + title: '8. Settings', + subSections: [ + { id: 'profile', title: 'Profile' }, + { id: 'security-and-privacy', title: 'Security and Privacy' }, + { id: 'subscription', title: 'Subscription' }, + { id: 'notifications', title: 'Notifications' }, + { id: 'appearance-and-units', title: 'Appearance and Units' }, + { id: 'data-import-and-export', title: 'Data Import and Export' }, + { id: 'account-actions', title: 'Account Actions' }, + ], + }, + { + id: 'subscription-tiers', + title: '9. Subscription Tiers and Pro Features', + subSections: [ + { id: 'tier-comparison', title: 'Tier Comparison' }, + { id: 'vin-camera-scanning', title: 'VIN Camera Scanning (Pro)' }, + { id: 'fuel-receipt-scanning', title: 'Fuel Receipt Scanning (Pro)' }, + { id: 'maintenance-receipt-scanning', title: 'Maintenance Receipt Scanning (Pro)' }, + { id: 'maintenance-manual-pdf', title: 'Maintenance Manual PDF (Pro)' }, + { id: 'email-ingestion', title: 'Email Ingestion (Pro)' }, + { id: 'shared-vehicle-documents', title: 'Shared Vehicle Documents (Pro)' }, + { id: 'community-station-submissions', title: 'Community Station Submissions (Pro)' }, + { id: 'managing-your-subscription', title: 'Managing Your Subscription' }, + ], + }, + { + id: 'mobile-experience', + title: '10. Mobile Experience', + subSections: [], + }, +]; diff --git a/frontend/src/pages/GuidePage/sections/DashboardSection.tsx b/frontend/src/pages/GuidePage/sections/DashboardSection.tsx new file mode 100644 index 0000000..d408b58 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/DashboardSection.tsx @@ -0,0 +1,156 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const DashboardSection = () => { + return ( +
+

2. Dashboard

+ +

+ After logging in, you land on the Dashboard -- your fleet headquarters. +

+ + {/* Your Fleet Overview */} +
+

Your Fleet Overview

+

+ What You See +

+ +

+ The Dashboard displays a "Your Fleet" heading with all your vehicles shown as cards in a grid layout. Each vehicle card shows: +

+ +
    +
  • + Vehicle icon -- A small colored indicator badge (varies by vehicle) +
  • +
  • + Vehicle name -- The nickname or full name (e.g., "Beast", "MERLOT") +
  • +
  • + Health status -- A green dot indicates "All clear" (no overdue maintenance); other colors indicate attention needed +
  • +
  • + Status text -- "All clear" or a maintenance alert message +
  • +
  • + Odometer reading -- Current mileage (e.g., "35,000 mi") +
  • +
+ + + +

+ Click any vehicle card to go directly to that vehicle's detail page. +

+ +

+ The vehicle health status system provides at-a-glance monitoring of your fleet: +

+ +
    +
  • + Green -- All maintenance is current, no action needed +
  • +
  • + Yellow -- Maintenance due soon (within the next 500 miles or 1 month) +
  • +
  • + Red -- Maintenance overdue or critical attention required +
  • +
+ +

+ The Dashboard layout adapts to your screen size. On desktop, vehicle cards display in a 3-column grid. On tablets, they adjust to 2 columns. On mobile devices, cards stack vertically for easy scrolling. +

+
+ + {/* Quick Actions */} +
+

Quick Actions

+

+ Two action buttons appear in the top-right corner of the Dashboard: +

+ + + +

+ These quick actions let you perform the most common tasks without navigating away from the Dashboard. +

+ +

+ The LOG FUEL button opens a modal where you can quickly record a fuel fill-up for any vehicle. This is the fastest way to log fuel after visiting a gas station. The modal pre-fills with the current date and time, and calculates MPG automatically based on your odometer reading. +

+ +

+ The + Add Vehicle button takes you directly to the Vehicles page with the add form expanded, ready for you to enter your new vehicle's details. +

+ +

Navigation Sidebar

+ +

+ The left sidebar provides access to all sections of the app: +

+ + + +

+ At the bottom of the sidebar you will see your email address and a Sign Out button. +

+ +

+ The header bar at the top shows a notification bell icon and a "Welcome back" greeting with your email. +

+ +

+ On mobile devices, the navigation experience changes: +

+ +
    +
  • + The left sidebar is hidden and replaced by a bottom navigation bar for quick access to Dashboard, Vehicles, and Stations +
  • +
  • + A floating action button (FAB) appears in the center with quick actions for logging fuel, adding vehicles, and more +
  • +
  • + The full menu is accessible via a hamburger menu in the header that slides up from the bottom +
  • +
+ + + +

+ The Dashboard is designed as your central hub for managing your entire fleet. From here, you can monitor vehicle health, access quick actions, and navigate to any section of the application. The responsive design ensures a consistent experience whether you are using a desktop computer, tablet, or mobile phone. +

+
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/DocumentsSection.tsx b/frontend/src/pages/GuidePage/sections/DocumentsSection.tsx new file mode 100644 index 0000000..887ac3d --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/DocumentsSection.tsx @@ -0,0 +1,102 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const DocumentsSection = () => { + return ( +
+

7. Documents

+ +

+ Click Documents in the sidebar. This page stores all your vehicle-related paperwork digitally. +

+ +

+ Documents Overview +

+ +

+ The page shows the title "Documents" with an Add Document button in the top-right corner. +

+ +

+ If you have no documents yet, you will see an empty state: +

+ +
    +
  • A document icon
  • +
  • "No Documents Yet"
  • +
  • "You haven't added any documents yet. Documents will appear here once you create them."
  • +
  • A Go to Vehicles button (since documents are associated with vehicles)
  • +
+ + + +

+ When documents exist, they appear in a list/grid with preview thumbnails, titles, document types, and associated vehicles. +

+ +

+ Adding a Document +

+ +

+ Click Add Document to open the "Add Document" modal with these fields: +

+ + + +

+ Click Create Document to save, or Cancel to discard. +

+ +

+ Document Types +

+ + + +

+ Insurance documents have additional fields: Insurance Company, Policy Number, Effective Date, Expiration Date, Coverage amounts (Bodily Injury, Property Damage), and Premium. +

+ +

+ Registration documents have additional fields: License Plate, Expiration Date, and Registration Cost. +

+ +

+ Documents with expiration dates will show countdown badges so you know when renewals are coming up. +

+ +
+

+ Pro Feature: When uploading a Maintenance Manual PDF, Pro and Enterprise users can check Scan for Maintenance Schedule to automatically extract a complete maintenance schedule from the document. See Maintenance Manual PDF Extraction (Pro) for the full workflow. +

+
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/FuelLogsSection.tsx b/frontend/src/pages/GuidePage/sections/FuelLogsSection.tsx new file mode 100644 index 0000000..53e153c --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/FuelLogsSection.tsx @@ -0,0 +1,142 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const FuelLogsSection = () => { + return ( +
+

4. Fuel Logs

+ + {/* Fuel Logs Overview */} +
+

Fuel Logs Overview

+

+ Click Fuel Logs in the sidebar to see the Fuel Logs page. At the top, you see summary statistics: +

+ + + +

+ Below the summary, a table lists all your fuel log entries. If you have no entries yet, you will see "No fuel logs yet." +

+ +

+ The + Add Fuel Log button is in the top-right corner. +

+ + +
+ + {/* Logging Fuel */} +
+

Logging Fuel

+

+ Click + Add Fuel Log (or the LOG FUEL button from the Dashboard) to open the "Log Fuel" modal. The modal title reads "Add Fuel Log" with a note showing your current unit system (e.g., "Displaying in Imperial (miles, gallons, MPG)"). +

+ +

Receipt Scanning

+

+ At the top of the form, click SCAN RECEIPT to use your camera to photograph a fuel receipt. The OCR system will automatically extract: +

+ +
    +
  • Fuel amount (gallons)
  • +
  • Cost per gallon
  • +
  • Total cost
  • +
  • Date and time
  • +
  • Fuel grade
  • +
  • Station name
  • +
+ +

+ You can review and edit any extracted values before saving. +

+ +

Form Fields

+ + + + +

+ Click Add Fuel Log to save the entry. The button is disabled until all required fields are completed. +

+
+ + {/* Receipt Scanning */} +
+

Receipt Scanning

+
+

+ Pro Feature: Receipt scanning requires a Pro or Enterprise subscription. See Fuel Receipt Scanning (Pro) for full details on what is extracted and the review workflow. +

+
+ +

+ The receipt scanning feature uses OCR technology: +

+ +
    +
  1. Click SCAN RECEIPT at the top of the Log Fuel form
  2. +
  3. Use your camera to photograph the receipt
  4. +
  5. The system extracts fuel data with confidence indicators
  6. +
  7. A review modal appears showing extracted values
  8. +
  9. Edit any incorrect values inline
  10. +
  11. Click Accept to auto-fill the form, or Reject to enter manually
  12. +
+ + +
+ + {/* Editing and Deleting Fuel Logs */} +
+

Editing and Deleting Fuel Logs

+

+ From the fuel logs table, each entry has action buttons: +

+ +
    +
  • + Edit -- Opens the fuel log in edit mode to update any fields +
  • +
  • + Delete -- Removes the fuel log entry (with confirmation) +
  • +
+
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/GasStationsSection.tsx b/frontend/src/pages/GuidePage/sections/GasStationsSection.tsx new file mode 100644 index 0000000..9e5718f --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/GasStationsSection.tsx @@ -0,0 +1,105 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const GasStationsSection = () => { + return ( +
+

6. Gas Stations

+ +

+ Click Gas Stations in the sidebar. This page helps you find gas stations near you and save your favorites. +

+ +

+ The page is split into two sections: +

+ +
    +
  • Left: An interactive Google Map showing station locations as markers
  • +
  • Right: Search controls
  • +
+ +

+ Finding Stations +

+ +

+ Search Options +

+ + + + + +

+ You can either use your current location OR manually enter an address. Search results appear below the map. +

+ +

+ Search Results +

+ +

+ Below the map, there are three tabs: +

+ + + +

+ Each station result shows: +

+ +
    +
  • Station name (e.g., "Costco Gas Station", "Mobil")
  • +
  • Street address and city
  • +
  • Star rating (community-verified)
  • +
  • Fuel grade badges (e.g., "93 Octane - w/ Ethanol")
  • +
  • Save/unsave button
  • +
+ +

+ Saved Stations +

+ +

+ Click the SAVED tab to see your favorite stations. Saved stations also appear as yellow star markers on the map. You can: +

+ +
    +
  • View station details
  • +
  • Remove a station from your saved list
  • +
  • Navigate on the map by clicking a station card
  • +
+ +

+ Premium 93 Stations +

+ +

+ Click the PREMIUM 93 tab to see your "Your Premium 93 Stations" -- stations that have been community-verified to carry genuine 93-octane fuel. This is especially useful for performance vehicles that require premium fuel. +

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/GettingStartedSection.tsx b/frontend/src/pages/GuidePage/sections/GettingStartedSection.tsx new file mode 100644 index 0000000..82bfb1a --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/GettingStartedSection.tsx @@ -0,0 +1,162 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const GettingStartedSection = () => { + return ( +
+

1. Getting Started

+ + {/* Creating an Account */} +
+

Creating an Account

+

+ Navigate to motovaultpro.com and click the Sign Up button in the top-right corner of the navigation bar. +

+ +

+ Sign Up Page +

+ +

+ The registration page displays the MotoVaultPro logo and a clean form with the following fields: +

+ + + + + +

+ After filling in all fields, click the Create Account button. +

+ +

+ If you already have an account, click the Login link at the bottom of the form. +

+ +

+ After registration, you will receive a verification email. Click the link in the email to verify your account before logging in. +

+
+ + {/* Logging In */} +
+

Logging In

+

+ Click the Login button in the top-right corner of the navigation bar. You will be redirected to the secure login page powered by Auth0. +

+ +

+ Login Page +

+ +

+ Enter your registered email address, then click Continue. On the next screen, enter your password and click Continue to log in. +

+ + + +

+ After successful authentication, you will be redirected to the Dashboard. +

+
+ + {/* Onboarding */} +
+

Onboarding

+

+ First-time users see an onboarding flow with three steps: +

+ +
    +
  1. + Preferences -- Choose your preferred unit system (Imperial or Metric), distance units, and notification preferences. +
  2. +
  3. + Add Your First Vehicle -- Enter your first vehicle's details (you can skip this step and add vehicles later). +
  4. +
  5. + Complete -- A welcome screen with quick links to get started exploring the app. +
  6. +
+ + + +

+ The onboarding experience is designed to get you up and running quickly. During the preferences step, you can configure: +

+ +
    +
  • Your preferred unit system (Imperial or Metric) for distance, volume, and fuel economy
  • +
  • Notification preferences for maintenance reminders
  • +
  • Default settings for fuel logging and vehicle tracking
  • +
+ +

+ The optional vehicle setup step allows you to add your first vehicle immediately, but you can skip it and add vehicles later from the Vehicles page. +

+ + + +

+ Once onboarding is complete, you will land on the Dashboard and can begin managing your fleet immediately. +

+
+ + {/* Trouble Logging In */} +
+

Trouble Logging In

+

+ If you are having trouble logging in, try the following password reset and account recovery options. +

+ +

+ Common login issues and solutions: +

+ +
    +
  • + Forgot Password -- Use the password reset link to receive a reset email +
  • +
  • + Account Not Verified -- Check your email for the verification link sent during signup +
  • +
  • + Locked Account -- Contact support if your account has been locked due to multiple failed login attempts +
  • +
  • + Browser Issues -- Try clearing your browser cache or using a different browser +
  • +
+ +

+ MotoVaultPro uses Auth0 for secure authentication, ensuring your account credentials are protected with industry-standard security measures including encryption and multi-factor authentication support. +

+
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/MaintenanceSection.tsx b/frontend/src/pages/GuidePage/sections/MaintenanceSection.tsx new file mode 100644 index 0000000..446aa69 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/MaintenanceSection.tsx @@ -0,0 +1,137 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const MaintenanceSection = () => { + return ( +
+

5. Maintenance

+ +

+ Click Maintenance in the sidebar. This page has two tabs: RECORDS and SCHEDULES. +

+ +

+ At the top is a Vehicle dropdown to select which vehicle you are viewing or adding maintenance for. +

+ + {/* Maintenance Records */} +
+

Maintenance Records

+

+ The RECORDS tab shows your maintenance history for the selected vehicle. Below the list is the "Add Maintenance Record" form. +

+ + +
+ + {/* Adding a Maintenance Record */} +
+

Adding a Maintenance Record

+

+ The form on the RECORDS tab includes: +

+ +

Receipt Upload

+
+

+ Pro Feature: Maintenance receipt scanning requires a Pro or Enterprise subscription. See Maintenance Receipt Scanning (Pro) for full details. +

+
+ +

+ Click the ADD RECEIPT button (dashed outline area) to upload or photograph a maintenance receipt. The OCR system can extract: +

+ +
    +
  • Category and service type
  • +
  • Cost
  • +
  • Date
  • +
  • Shop name
  • +
+ +

Form Fields

+ + +

+ Click Add Record to save the maintenance record. +

+ +

Maintenance Categories

+ + + +
+ + {/* Maintenance Schedules */} +
+

Maintenance Schedules

+

+ Click the SCHEDULES tab to set up recurring maintenance reminders. +

+ + +
+ + {/* Creating a Schedule */} +
+

Creating a Schedule

+

+ The "Create Maintenance Schedule" form includes: +

+ + + +

+ Click Create Schedule to save. +

+ +

+ Below the form, the "Maintenance Schedules" section lists all active schedules for the selected vehicle, showing when each service is next due. +

+
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/MobileExperienceSection.tsx b/frontend/src/pages/GuidePage/sections/MobileExperienceSection.tsx new file mode 100644 index 0000000..a2d8100 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/MobileExperienceSection.tsx @@ -0,0 +1,69 @@ +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const MobileExperienceSection = () => { + return ( +
+

10. Mobile Experience

+ +

+ MotoVaultPro is fully responsive and works on both desktop and mobile devices. +

+ +

+ Mobile Navigation +

+ +

+ On mobile, the sidebar is replaced by: +

+ +
    +
  • + A bottom navigation bar with icons for: Dashboard, Vehicles, Stations +
  • +
  • + A floating action button (FAB) in the center with quick actions: +
      +
    • Log Fuel
    • +
    • Add Vehicle
    • +
    • Add Document
    • +
    • Add Maintenance
    • +
    +
  • +
  • + A hamburger menu (accessed from the header) that slides up from the bottom, providing access to all sections: Dashboard, Vehicles, Log Fuel, Maintenance, Documents, Settings +
  • +
+ + + + + +

+ Mobile Optimizations +

+ +
    +
  • Touch-friendly buttons and targets (minimum 44px)
  • +
  • Swipe gestures for image viewing
  • +
  • Camera integration for VIN scanning and receipt capture
  • +
  • Full-screen forms for data entry
  • +
  • Responsive card layouts that stack vertically on smaller screens
  • +
+ +

+ All features available on desktop are also available on mobile -- no functionality is lost on smaller screens. +

+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/SettingsSection.tsx b/frontend/src/pages/GuidePage/sections/SettingsSection.tsx new file mode 100644 index 0000000..a824cc7 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/SettingsSection.tsx @@ -0,0 +1,148 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const SettingsSection = () => { + return ( +
+

8. Settings

+ +

+ Click Settings in the sidebar to manage your account, preferences, and data. +

+ +

+ Profile +

+ +

+ The Profile section displays your account information: +

+ + + + + +

+ Click the Edit button to update your display name or notification email. +

+ +

+ Security and Privacy +

+ +

+ The Security & Privacy row shows "Password, two-factor authentication" with a Manage button. Click it to: +

+ +
    +
  • Change your password
  • +
  • Set up two-factor authentication
  • +
  • Manage active sessions
  • +
  • Log out all devices
  • +
+ +

+ Subscription +

+ +

+ Shows your current subscription plan with a Manage button. +

+ + + +

+ "Upgrade to Pro or Enterprise for more features and vehicle slots." +

+ +

+ Click Manage to view plan details, change your subscription, manage payment methods, and view billing history. +

+ +

+ Notifications +

+ + + +

+ Toggle each setting on or off. +

+ +

+ Appearance and Units +

+ + + +

+ The unit system you select here applies throughout the entire application -- Dashboard, Fuel Logs, Maintenance, and Vehicle Details all update to reflect your preference. +

+ +

+ Data Import and Export +

+ + + +

+ Export creates a downloadable archive of all your data. Import accepts a previously exported backup file to restore your data. +

+ +

+ Account Actions +

+ +

+ At the bottom of the Settings page: +

+ + +
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/SubscriptionSection.tsx b/frontend/src/pages/GuidePage/sections/SubscriptionSection.tsx new file mode 100644 index 0000000..5a6c77a --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/SubscriptionSection.tsx @@ -0,0 +1,384 @@ +import { GuideTable } from '../components/GuideTable'; + +export const SubscriptionSection = () => { + return ( +
+

9. Subscription Tiers and Pro Features

+ +

+ MotoVaultPro offers three subscription tiers. Higher tiers automatically include all features from lower tiers. +

+ +

+ Tier Comparison +

+ + + +

+ When you attempt to use a Pro feature on the Free tier, an Upgrade Required dialog appears explaining the feature and offering a direct link to upgrade. +

+ +

+ VIN Camera Scanning and Decode (Pro) +

+ +

+ What it does: Use your device camera to photograph your vehicle's VIN plate, and the system automatically reads the VIN using OCR (Optical Character Recognition) and decodes it from the NHTSA database. +

+ +

+ How to use it: +

+ +
    +
  1. Go to Vehicles and click + Add Vehicle
  2. +
  3. In the VIN Number field, click the camera icon
  4. +
  5. Point your camera at the VIN plate on your vehicle (typically on the driver-side dashboard or door jamb)
  6. +
  7. The OCR system reads the 17-character VIN from the image
  8. +
  9. A VIN OCR Review modal appears showing the detected VIN with confidence indicators
  10. +
  11. Confirm or correct the VIN, then click Accept
  12. +
  13. Click the Decode VIN button
  14. +
  15. The system queries the NHTSA database and auto-populates: Year, Make, Model, Engine, Transmission, and Trim
  16. +
  17. Review the pre-filled fields and complete the remaining details
  18. +
+ +

+ This eliminates manual data entry errors and ensures accurate vehicle specifications. +

+ +

+ Fuel Receipt Scanning (Pro) +

+ +

+ What it does: Photograph a fuel receipt and the OCR system extracts all relevant data, automatically filling in your fuel log entry. +

+ +

+ How to use it: +

+ +
    +
  1. Open the Log Fuel modal (from Dashboard or Fuel Logs page)
  2. +
  3. Click the SCAN RECEIPT button at the top of the form
  4. +
  5. Use your camera to photograph the fuel receipt
  6. +
  7. The system processes the image and extracts relevant data
  8. +
+ + + +
    +
  1. A Receipt OCR Review modal appears showing all extracted values with confidence scores
  2. +
  3. Each field can be edited inline if the OCR got something wrong
  4. +
  5. The station name is automatically matched against known gas stations in the system
  6. +
  7. Click Accept to auto-fill the Log Fuel form with the extracted values
  8. +
  9. Click Reject to discard the scan and enter data manually
  10. +
  11. Review the pre-filled form and click Add Fuel Log
  12. +
+ +

+ Tips for best results: +

+ +
    +
  • Photograph the receipt on a flat, well-lit surface
  • +
  • Ensure the entire receipt is visible in the frame
  • +
  • Avoid wrinkled or faded receipts when possible
  • +
+ +

+ Maintenance Receipt Scanning (Pro) +

+ +

+ What it does: Photograph a maintenance or service receipt to automatically extract service details into a maintenance record. +

+ +

+ How to use it: +

+ +
    +
  1. Go to Maintenance and select a vehicle
  2. +
  3. On the RECORDS tab, click the ADD RECEIPT button (dashed outline area)
  4. +
  5. Use your camera to photograph the service receipt
  6. +
  7. The system processes the image and extracts relevant data
  8. +
+ + + +
    +
  1. A Maintenance Receipt Review modal shows extracted values with confidence indicators
  2. +
  3. Edit any incorrect values inline
  4. +
  5. Click Accept to auto-fill the maintenance record form
  6. +
  7. Review and click Add Record
  8. +
+ +

+ Maintenance Manual PDF Extraction (Pro) +

+ +

+ What it does: Upload your vehicle's owner's manual or maintenance manual as a PDF, and the system automatically extracts the recommended maintenance schedule -- creating maintenance schedules with the correct intervals for your specific vehicle. +

+ +

+ How to use it: +

+ +
    +
  1. Go to Documents and click Add Document
  2. +
  3. Select your vehicle and choose Maintenance Manual as the document type
  4. +
  5. Upload the PDF file
  6. +
  7. Check the Scan for Maintenance Schedule checkbox (Pro feature -- indicated by a lock icon for Free tier users)
  8. +
  9. Click Create Document
  10. +
  11. The system submits the PDF for asynchronous processing
  12. +
  13. A progress indicator shows while the document is being analyzed
  14. +
  15. When processing completes, the Maintenance Schedule Review screen appears
  16. +
+ + + +
    +
  1. Check the boxes next to the maintenance items you want to create
  2. +
  3. Edit any service names, intervals, or details inline
  4. +
  5. Click Create Selected Schedules to batch-create all selected items as maintenance schedules for your vehicle
  6. +
+ +

+ This turns a 50-page owner's manual into a complete set of maintenance reminders in minutes. +

+ +

+ Email Ingestion (Pro) +

+ +

+ What it does: Forward vehicle-related emails (service receipts, insurance documents, registration notices) to a dedicated email address, and they automatically appear in your MotoVaultPro account ready to be associated with a vehicle. +

+ +

+ How to use it: +

+ +
    +
  1. Forward any vehicle-related email to your dedicated MotoVaultPro ingestion address
  2. +
  3. The system processes the email and any attachments
  4. +
  5. On your Dashboard, a Pending Associations banner appears showing how many items are waiting
  6. +
  7. Click the banner to open the Pending Association List
  8. +
  9. For each pending item, you see a preview of the document or receipt and a vehicle selector dropdown
  10. +
  11. Select the correct vehicle for each item and click Associate
  12. +
  13. Or click Discard to remove items you do not want
  14. +
+ +

+ Bulk actions are available to discard all pending items at once. +

+ +

+ This is especially useful for: +

+ +
    +
  • Forwarding digital receipts from auto parts stores
  • +
  • Forwarding service confirmation emails from your mechanic
  • +
  • Forwarding insurance policy documents from your provider
  • +
  • Forwarding registration renewal notices
  • +
+ +

+ Shared Vehicle Documents (Pro) +

+ +

+ What it does: Associate a single document with multiple vehicles. Useful for fleet insurance policies, multi-vehicle service agreements, or shared maintenance contracts. +

+ +

+ How to use it: +

+ +
    +
  1. Open an existing document's detail page
  2. +
  3. In the Shared Vehicles section, click Add Vehicle
  4. +
  5. Select additional vehicles from the dropdown
  6. +
  7. The document now appears in the Documents section for each associated vehicle
  8. +
  9. To remove a vehicle association, click the Remove button next to that vehicle
  10. +
+ +

+ Community Station Submissions (Pro) +

+ +

+ What it does: Submit new gas stations to the MotoVaultPro community database, helping other enthusiasts find quality fuel locations -- especially stations carrying true 93-octane premium fuel. +

+ +

+ How to use it: +

+ +
    +
  1. Go to Gas Stations
  2. +
  3. Look for the option to submit a new community station
  4. +
  5. Fill in the submission form
  6. +
+ + + +
    +
  1. Submit for community review
  2. +
  3. An admin reviews and approves or rejects the submission
  4. +
  5. Approved stations appear in the PREMIUM 93 tab and search results with a community-verified badge
  6. +
+ +

+ Managing Your Subscription +

+ +

+ Viewing Your Plan +

+ +

+ Go to Settings and find the Subscription section. It shows your current plan (FREE, Pro, or Enterprise) with a Manage button. +

+ +

+ Upgrading +

+ +
    +
  1. Click Manage in the Subscription section
  2. +
  3. The Subscription page shows tier comparison cards with pricing
  4. +
  5. Toggle between Monthly and Annual billing (annual saves money)
  6. +
  7. Click Upgrade on the plan you want
  8. +
  9. Enter your payment details using the secure Stripe payment form
  10. +
  11. Your new features are available immediately
  12. +
+ +

+ Payment Methods +

+ +
    +
  • Payment is processed through Stripe (credit/debit cards)
  • +
  • You can save a card for recurring billing
  • +
  • Update or remove your payment method at any time
  • +
  • View billing history and download invoices as PDFs
  • +
+ +

+ Billing History +

+ +

+ The billing history table shows all past invoices with: +

+ +
    +
  • Date
  • +
  • Description
  • +
  • Amount
  • +
  • Status (Paid, Pending, Failed)
  • +
  • Download PDF button for each invoice
  • +
+ +

+ Downgrading +

+ +

+ If you downgrade from a higher tier, you may need to reduce your vehicles to fit within the lower tier's limit: +

+ +
    +
  1. Click Downgrade on the lower plan
  2. +
  3. A Vehicle Selection dialog appears if you exceed the new tier's vehicle limit
  4. +
  5. Select which vehicles to keep (e.g., keep 2 for Free tier)
  6. +
  7. A warning explains that removed vehicles and their data will be deleted
  8. +
  9. Confirm the downgrade
  10. +
+ + + +

+ Cancelling +

+ +
    +
  1. On the Subscription page, click Cancel Subscription
  2. +
  3. A confirmation dialog appears with retention options
  4. +
  5. Confirm cancellation
  6. +
  7. Your subscription remains active until the end of the current billing period
  8. +
  9. After expiration, your account reverts to the Free tier
  10. +
  11. Click Reactivate at any time before expiration to keep your plan
  12. +
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/VehiclesSection.tsx b/frontend/src/pages/GuidePage/sections/VehiclesSection.tsx new file mode 100644 index 0000000..b363a54 --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/VehiclesSection.tsx @@ -0,0 +1,221 @@ +import { GuideTable } from '../components/GuideTable'; +import { GuideScreenshot } from '../components/GuideScreenshot'; + +export const VehiclesSection = () => { + return ( +
+

3. Vehicles

+ + {/* Viewing Your Vehicles */} +
+

Viewing Your Vehicles

+

+ Click Vehicles in the sidebar to see the "My Vehicles" page. This page shows: +

+ +
    +
  • + Search bar -- Search vehicles by name, make, model, or VIN +
  • +
  • + + Add Vehicle button -- Top-right corner +
  • +
  • + Vehicle cards in a grid layout (3 columns on desktop), each displaying: +
      +
    • Manufacturer logo (e.g., Chevrolet bowtie, GMC logo)
    • +
    • Vehicle nickname or full name
    • +
    • Year, Make, and Model
    • +
    • License plate number
    • +
    • Current odometer reading (in miles or kilometers)
    • +
    • Edit (pencil icon) and Delete (trash icon) buttons at the bottom of each card
    • +
    +
  • +
+ + +
+ + {/* Adding a Vehicle */} +
+

Adding a Vehicle

+

+ Click the + Add Vehicle button to expand the "Add New Vehicle" form directly on the Vehicles page. The form has the following sections: +

+ +

Vehicle Photo

+

+ Upload a photo of your vehicle. Click ADD PHOTO to select an image file. +

+
    +
  • Accepted formats: JPEG or PNG
  • +
  • Maximum file size: 5MB
  • +
+ +

VIN Number

+

+ Enter your vehicle's 17-character VIN (Vehicle Identification Number). +

+
    +
  • Type the VIN manually in the text field, OR
  • +
  • Click the camera icon to scan the VIN using your device camera (uses OCR technology)
  • +
  • Click the Decode VIN button to automatically populate vehicle details from the VIN
  • +
+

+ Note: VIN is optional if you provide a License Plate instead. +

+ +

Vehicle Specifications

+

+ These fields use cascading dropdowns -- each selection narrows the options for the next field: +

+ + + + + +

Additional Details

+ + +

Purchase Information

+ + +

+ Click Add Vehicle to save, or Cancel to discard. +

+
+ + {/* VIN Decode */} +
+

VIN Decode

+
+

+ Pro Feature: VIN camera scanning and automatic decode require a Pro or Enterprise subscription. Free tier users can still type a VIN manually. See VIN Camera Scanning and Decode (Pro) for full details. +

+
+ +

+ The VIN Decode feature automatically fills in vehicle details from a VIN: +

+ +
    +
  1. Enter or scan your 17-character VIN
  2. +
  3. Click the Decode VIN button
  4. +
  5. The system looks up the VIN and auto-populates: Year, Make, Model, Engine, Transmission, and Trim
  6. +
  7. Review the pre-filled fields and make any corrections
  8. +
  9. Continue filling in the remaining fields (Nickname, Color, etc.)
  10. +
+ + +
+ + {/* Vehicle Detail Page */} +
+

Vehicle Detail Page

+

+ Click any vehicle card (from Dashboard or Vehicles list) to open the Vehicle Detail Page. This page shows everything about a single vehicle: +

+ +

Header Area

+
    +
  • Back arrow and BACK link to return to the previous page
  • +
  • Vehicle nickname as the page title (e.g., "Beast")
  • +
  • Edit Vehicle button (top-right)
  • +
  • Quick action buttons: Add Fuel Log and Add Maintenance
  • +
+ +

Vehicle Details Section

+
    +
  • Manufacturer logo
  • +
  • Full vehicle description (e.g., "2022 Chevrolet Silverado 2500HD")
  • +
  • VIN Number
  • +
  • Year, Make, and Model (displayed in a 3-column row)
  • +
  • Trim, Engine, and Transmission (displayed in a 3-column row)
  • +
  • Nickname
  • +
  • Color and License Plate (side by side)
  • +
  • Current Odometer Reading
  • +
+ + + +

Purchase Information Section

+
    +
  • Purchase Price
  • +
  • Purchase Date
  • +
+ +

Ownership Costs Section

+
    +
  • Tracks insurance, registration, taxes, and other recurring vehicle costs
  • +
  • Shows "No ownership costs recorded yet" until costs are added
  • +
+ +

Vehicle Records Section

+
    +
  • A table showing all records associated with this vehicle (fuel logs, maintenance records)
  • +
  • Columns: Date, Type, Summary, Amount, Actions
  • +
  • Filter dropdown to filter by record type (All, Fuel, Maintenance)
  • +
+
+ + {/* Editing a Vehicle */} +
+

Editing a Vehicle

+

+ From the Vehicle Detail Page, click the Edit Vehicle button. This opens the vehicle form pre-filled with the current values. Make your changes and save. +

+

+ From the Vehicles list, click the pencil icon on any vehicle card to edit it directly. +

+
+ + {/* Deleting a Vehicle */} +
+

Deleting a Vehicle

+

+ From the Vehicles list, click the trash icon on any vehicle card. You will be asked to confirm the deletion. Deleting a vehicle is permanent and removes all associated records. +

+
+
+ ); +}; diff --git a/frontend/src/pages/GuidePage/sections/index.ts b/frontend/src/pages/GuidePage/sections/index.ts new file mode 100644 index 0000000..5a6ce7b --- /dev/null +++ b/frontend/src/pages/GuidePage/sections/index.ts @@ -0,0 +1,12 @@ +import { lazy } from 'react'; + +export const GettingStartedSection = lazy(() => import('./GettingStartedSection').then(m => ({ default: m.GettingStartedSection }))); +export const DashboardSection = lazy(() => import('./DashboardSection').then(m => ({ default: m.DashboardSection }))); +export const VehiclesSection = lazy(() => import('./VehiclesSection').then(m => ({ default: m.VehiclesSection }))); +export const FuelLogsSection = lazy(() => import('./FuelLogsSection').then(m => ({ default: m.FuelLogsSection }))); +export const MaintenanceSection = lazy(() => import('./MaintenanceSection').then(m => ({ default: m.MaintenanceSection }))); +export const GasStationsSection = lazy(() => import('./GasStationsSection').then(m => ({ default: m.GasStationsSection }))); +export const DocumentsSection = lazy(() => import('./DocumentsSection').then(m => ({ default: m.DocumentsSection }))); +export const SettingsSection = lazy(() => import('./SettingsSection').then(m => ({ default: m.SettingsSection }))); +export const SubscriptionSection = lazy(() => import('./SubscriptionSection').then(m => ({ default: m.SubscriptionSection }))); +export const MobileExperienceSection = lazy(() => import('./MobileExperienceSection').then(m => ({ default: m.MobileExperienceSection }))); diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index ff0492a..88a65c5 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -6,10 +6,9 @@ import { FeaturesGrid } from './HomePage/FeaturesGrid'; import { motion } from 'framer-motion'; export const HomePage = () => { - const { loginWithRedirect, isAuthenticated, logout } = useAuth0(); + const { loginWithRedirect, isAuthenticated } = useAuth0(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); - const [sessionCleared, setSessionCleared] = useState(false); const navigate = useNavigate(); useEffect(() => { @@ -42,22 +41,6 @@ export const HomePage = () => { navigate('/signup'); }; - const handleClearSession = async () => { - try { - const { indexedDBStorage } = await import('../core/utils/indexeddb-storage'); - await indexedDBStorage.clearAll(); - Object.keys(localStorage).forEach(key => { - if (key.startsWith('@@auth0')) localStorage.removeItem(key); - }); - logout({ openUrl: false }); - setSessionCleared(true); - setTimeout(() => setSessionCleared(false), 3000); - } catch (error) { - console.error('[HomePage] Failed to clear session:', error); - window.location.reload(); - } - }; - return (
{/* Navigation Bar */} @@ -89,6 +72,12 @@ export const HomePage = () => { About + + Guide + -
{/* Mobile Menu Button */} @@ -160,6 +143,12 @@ export const HomePage = () => { > About + + Guide + - )} diff --git a/frontend/src/pages/HomePage/FeatureCard.tsx b/frontend/src/pages/HomePage/FeatureCard.tsx index 8fc2115..ca3d900 100644 --- a/frontend/src/pages/HomePage/FeatureCard.tsx +++ b/frontend/src/pages/HomePage/FeatureCard.tsx @@ -5,31 +5,34 @@ interface FeatureCardProps { description: string; imageSrc: string; imageAlt: string; + href: string; } -export const FeatureCard = ({ title, description, imageSrc, imageAlt }: FeatureCardProps) => { +export const FeatureCard = ({ title, description, imageSrc, imageAlt, href }: FeatureCardProps) => { return ( -
- + ); }; diff --git a/frontend/src/pages/HomePage/FeaturesGrid.tsx b/frontend/src/pages/HomePage/FeaturesGrid.tsx index b45e2d7..d3f10eb 100644 --- a/frontend/src/pages/HomePage/FeaturesGrid.tsx +++ b/frontend/src/pages/HomePage/FeaturesGrid.tsx @@ -26,6 +26,7 @@ type Feature = { title: string; description: string; image: UnsplashImageSpec; + href: string; }; // Centralize Unsplash identifiers so you only update them in one place if needed. @@ -69,6 +70,7 @@ const features: readonly Feature[] = [ { title: 'Vehicle Management', description: 'Track all your vehicles in one centralized location with detailed information and history.', + href: '/guide#vehicles', image: { photoId: '1503376780353-7e6692767b70', alt: 'Vehicle Management', @@ -77,6 +79,7 @@ const features: readonly Feature[] = [ { title: 'Fuel Log Tracking', description: 'Monitor fuel consumption, costs, and efficiency across all your vehicles.', + href: '/guide#fuel-logs', image: { photoId: '1529369623266-f5264b696110', alt: 'Fuel Log Tracking', @@ -85,6 +88,7 @@ const features: readonly Feature[] = [ { title: 'Maintenance Records', description: 'Keep detailed maintenance logs and never miss scheduled service appointments.', + href: '/guide#maintenance', image: { photoId: '1486262715619-67b85e0b08d3', alt: 'Maintenance Records', @@ -93,6 +97,7 @@ const features: readonly Feature[] = [ { title: 'Document Storage', description: 'Store and organize all vehicle documents, receipts, and important paperwork.', + href: '/guide#documents', image: { photoId: '1568605117036-5fe5e7bab0b7', alt: 'Document Storage', @@ -101,6 +106,7 @@ const features: readonly Feature[] = [ { title: 'Fuel Stations', description: 'Find and track your favorite fuel locations. Community verified stations with Premium 93 Octane.', + href: '/guide#gas-stations', image: { photoId: '1572281335102-5f780686ee91', alt: 'Fuel Stations', @@ -110,6 +116,7 @@ const features: readonly Feature[] = [ { title: 'Reports & Analytics', description: 'Generate detailed reports on costs, mileage, and vehicle performance.', + href: '/guide#dashboard', image: { photoId: '1551288049-bebda4e38f71', alt: 'Reports & Analytics', @@ -118,6 +125,7 @@ const features: readonly Feature[] = [ { title: 'Reminders', description: 'Set up automated reminders for maintenance, registration, and insurance renewals.', + href: '/guide#maintenance-schedules', image: { photoId: '1434494878577-86c23bcb06b9', alt: 'Reminders', @@ -126,6 +134,7 @@ const features: readonly Feature[] = [ { title: 'Data Export', description: 'Export your data in various formats for reporting and record keeping.', + href: '/guide#data-import-and-export', image: { photoId: '1460925895917-afdab827c52f', alt: 'Data Export', @@ -152,6 +161,7 @@ export const FeaturesGrid = () => { description={feature.description} imageSrc={buildUnsplashUrl(feature.image)} imageAlt={feature.image.alt} + href={feature.href} /> ))}
diff --git a/frontend/src/pages/__tests__/GuidePage.test.tsx b/frontend/src/pages/__tests__/GuidePage.test.tsx new file mode 100644 index 0000000..2dd274a --- /dev/null +++ b/frontend/src/pages/__tests__/GuidePage.test.tsx @@ -0,0 +1,98 @@ +import { render, screen } from '@testing-library/react'; + +jest.mock('@auth0/auth0-react', () => ({ + useAuth0: () => ({ + isAuthenticated: false, + loginWithRedirect: jest.fn(), + }), +})); + +jest.mock('react-router-dom', () => ({ + useNavigate: () => jest.fn(), +})); + +// Mock MUI Accordion to avoid jsdom layout issues +jest.mock('@mui/material', () => { + const actual = jest.requireActual('@mui/material'); + return { + ...actual, + Accordion: ({ children, expanded }: any) => ( +
+ {children} +
+ ), + AccordionSummary: ({ children }: any) =>
{children}
, + AccordionDetails: ({ children }: any) =>
{children}
, + }; +}); + +jest.mock('@mui/icons-material/ExpandMore', () => ({ + __esModule: true, + default: () => , +})); + +import { GuidePage } from '../GuidePage/GuidePage'; +import { guideSections } from '../GuidePage/guideTypes'; + +describe('GuidePage', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders page heading and subheading', () => { + render(); + expect(screen.getByText('User Guide')).toBeInTheDocument(); + expect( + screen.getByText(/Precision Vehicle Management/) + ).toBeInTheDocument(); + }); + + it('renders all 10 section headings', () => { + render(); + + const expectedHeadings = [ + '1. Getting Started', + '2. Dashboard', + '3. Vehicles', + '4. Fuel Logs', + '5. Maintenance', + '6. Gas Stations', + '7. Documents', + '8. Settings', + '9. Subscription Tiers and Pro Features', + '10. Mobile Experience', + ]; + + expectedHeadings.forEach((heading) => { + expect(screen.getByRole('heading', { name: heading })).toBeInTheDocument(); + }); + }); + + it('renders TOC with correct section titles', () => { + render(); + + guideSections.forEach((section) => { + const matches = screen.getAllByText(section.title); + expect(matches.length).toBeGreaterThanOrEqual(1); + }); + }); + + it('renders navigation bar with Guide link highlighted', () => { + render(); + + const guideLinks = screen.getAllByText('Guide'); + expect(guideLinks.length).toBeGreaterThanOrEqual(1); + }); + + it('renders GuideScreenshot components with loading="lazy"', () => { + render(); + + const images = document.querySelectorAll('img[loading="lazy"]'); + expect(images.length).toBeGreaterThan(0); + }); + + it('renders footer with copyright', () => { + render(); + expect(screen.getByText(/FB Technologies LLC/)).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/shared-minimal/components/mobile/HamburgerDrawer.tsx b/frontend/src/shared-minimal/components/mobile/HamburgerDrawer.tsx index da21282..a4b9e73 100644 --- a/frontend/src/shared-minimal/components/mobile/HamburgerDrawer.tsx +++ b/frontend/src/shared-minimal/components/mobile/HamburgerDrawer.tsx @@ -20,6 +20,7 @@ import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRound import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded'; import BuildRoundedIcon from '@mui/icons-material/BuildRounded'; import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded'; +import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded'; import { MobileScreen } from '../../../core/store/navigation'; // iOS swipeable drawer configuration @@ -41,6 +42,7 @@ interface MenuItem { // Menu items from bottom to top (reversed order in array for rendering) const menuItems: MenuItem[] = [ { screen: 'Settings', label: 'Settings', icon: }, + { screen: 'Guide' as MobileScreen, label: 'Guide', icon: }, { screen: 'Documents', label: 'Documents', icon: }, { screen: 'Maintenance', label: 'Maintenance', icon: }, { screen: 'Log Fuel', label: 'Log Fuel', icon: }, @@ -57,6 +59,12 @@ export const HamburgerDrawer: React.FC = ({ const theme = useTheme(); const handleNavigate = (screen: MobileScreen) => { + // Guide is a public route outside /garage/* shell + if (screen === ('Guide' as MobileScreen)) { + window.location.href = '/guide'; + onClose(); + return; + } onNavigate(screen); onClose(); };