From 38deca45623c49f4bb91e31055cce62010cfd9af Mon Sep 17 00:00:00 2001 From: Ned Halksworth Date: Wed, 27 May 2026 14:28:46 +0100 Subject: [PATCH 1/2] feat: add AGENTS.MD and CLAUDE.MD --- AGENTS.md | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0d4e8e4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,220 @@ +# Project Overview + +Red Bull Intake Tracker is a premium web-based tracking dashboard designed for tracking caffeine +and beverage consumption, with a strong focus on Red Bull products. Built using React, Vite, and +TypeScript, it allows users to record intake (amount, flavour, size, price, timestamp, and location), +monitor daily spending and caffeine limits, view structured trends and streaks, import/export +data via styled Excel or JSON formats, and engage in real-time encrypted-compatible dialogue with an +AI wellness coach powered by a serverless Ollama proxy endpoint. Synchronized dynamically with +Appwrite Cloud databases using secure row-level document permissions, it delivers a highly reactive, +personalized, and privacy-first self-tracking experience. + +## Repository Structure + +- `api/` – Contains serverless backend handlers, including the API gateway proxy for Ollama chat endpoints. +- `dist/` – Contains static HTML, JavaScript, and CSS bundle files output by the production build process. +- `node_modules/` – Stores third-party library dependencies and packages managed by npm. +- `scripts/` – Houses automation scripts, including database schema configuration tools for Appwrite. +- `src/` – Contains client-side React source code, components, utility models, and stylesheets. + - `src/components/` – Reusable UI panel elements, forms, and splash screen wrappers. + - `src/data/` – Static configurations, including theme lists and built-in flavours mapping. + - `src/lib/` – Business logic engines for calculations, file parsers, and Appwrite client connections. + +## Build & Development Commands + +Use the following shell-ready commands to install dependencies, run the application, lint the code, +and manage the cloud database. + +### Dependency Installation +```bash +npm install +``` + +### Local Development Server +Starts a local development server at `http://localhost:5173`. +```bash +npm run dev +``` + +### Production Build & Bundling +Performs TypeScript diagnostic type-checks and compiles the application into the `dist/` directory. +```bash +npm run build +``` + +### Production Preview +Runs a local web server to preview the built production bundle. +```bash +npm run preview +``` + +### Code Linting +Runs ESLint over TypeScript files to identify syntax issues and code style warnings. +```bash +npm run lint +``` + +### Appwrite Cloud Database Setup +Automatically provisions the databases, tables, columns, and indexes on the configured Appwrite instance. +```bash +npm run setup:appwrite +``` + +### Automated Testing +> TODO: Add automated test suite command (e.g., `npm run test` using Vitest or Jest). + +### Application Deployment +> TODO: Add production deployment pipeline command (e.g., Vercel, Netlify, or Docker deploy). + +## Code Style & Conventions + +- **Language**: TypeScript is strictly required for all UI components and logic scripts; JavaScript + is limited to serverless handlers and build scripting. +- **Strict Checks**: TypeScript's `strict` compiler option is enabled; avoid using `any` and ensure + all parameters and return values are explicitly typed. +- **Formatting**: Code should be formatted with 2-space indentation, trailing commas where supported, + and double quotes for JSX/TSX properties. +- **Linting**: Rules are governed by ESLint (`eslint.config.js`), extending the standard TypeScript + and React Hooks rulesets. +- **Naming Conventions**: + - React components and files use PascalCase (e.g., `CoachPanel.tsx`). + - Business logic, utilities, and helper hooks use camelCase (e.g., `appwriteEntries.ts`, `useCoachSession.ts`). + - Constants and static metadata arrays use UPPER_SNAKE_CASE (e.g., `BUILT_IN_FLAVOURS`). +- **Imports**: Prefer explicit ES module imports (`import { ... } from "..."`). Avoid wildcards. +- **Commit Messages**: + - > TODO: Define commit message guidelines and templates (e.g., Conventional Commits). + +## Architecture Notes + +### Flow Architecture Diagram + +```mermaid +graph TD + subgraph Frontend [Vite React Client] + App[App.tsx - Core State & Shell] + Components[Onboarding, CoachPanel, Limits, etc.] + LibMetrics[metrics.ts & userLimits.ts] + LibIO[excel.ts & storage.ts] + LibTheme[themeTokens.ts & themes.ts] + HookCoach[useCoachSession.ts] + end + + subgraph BackendAPI [API & Proxies] + ViteProxy[Vite Dev Server Middleware] + VercelHandler[api/ollama-chat.js Serverless Function] + end + + subgraph External [External Services] + AppwriteCloud[Appwrite Cloud / TablesDB] + OllamaAPI[Ollama Cloud API] + end + + App --> Components + App --> LibMetrics + App --> LibIO + App --> LibTheme + App --> HookCoach + + HookCoach -- "/api/ollama-chat (Local)" --> ViteProxy + HookCoach -- "/api/ollama-chat (Prod)" --> VercelHandler + + ViteProxy -- "Headers Auth" --> OllamaAPI + VercelHandler -- "Headers Auth" --> OllamaAPI + + App --> AppwriteCloud +``` + +### Component Roles & Data Flow + +1. **State Orchestration (`src/App.tsx`)**: + Acts as the monolithic hub of the frontend. It manages user authentication states, currently + selected views, active database operations, modals, onboarding triggers, and theme settings. + +2. **Metrics & Limits (`src/lib/metrics.ts`, `src/lib/userLimits.ts`)**: + Process raw intakes to extract total cans, spendings, caffeine absorption, hydration estimates, + streaks, and coordinate warnings when user limits are breached or bedtime approaches. + +3. **External Data Codecs (`src/lib/excel.ts`, `src/lib/storage.ts`)**: + Implement styled spreadsheet formatting with `exceljs`, data sanity validation, duplicate-aware + import preview engines, and local JSON backup/restore modules. + +4. **Dynamic Styling (`src/lib/themeTokens.ts`, `src/data/themes.ts`)**: + Computes CSS tokens dynamically from a selection of Vocaloid or beverage-themed configurations, + writing variables into the document root for real-time visual styling modifications. + +5. **Cloud Synced State (`src/lib/appwrite.ts`, `src/lib/appwriteEntries.ts`, `src/lib/coachChats.ts`)**: + Establishes client tunnels to Appwrite's serverless TablesDB backend, running row-secured + CRUD actions bound strictly to the current `userId`. + +6. **AI Coach Chatbot (`src/lib/useCoachSession.ts`, `api/ollama-chat.js`)**: + A state-machine custom hook that pipelines user questions, injects historical intake aggregates, + and streams responses from DeepSeek through server-side Ollama proxy tunnels. + +## Testing Strategy + +The repository does not currently feature automated test files. Testing is executed manually. + +### Unit & Integration Testing +- > TODO: Configure unit and integration tests (e.g., Vitest + React Testing Library) to validate + metrics computations, limits checks, and file importing codecs. + +### End-to-End (E2E) Testing +- > TODO: Introduce E2E test suites (e.g., Playwright or Cypress) to cover authentication paths, + entry additions, theme switches, and chatbot conversation loops. + +### Continuous Integration (CI) +- > TODO: Establish a GitHub Actions workflow pipeline to run linters, type checks, and tests on + every branch commit or pull request. + +## Security & Compliance + +- **Authentication**: Delegated entirely to Appwrite's built-in OAuth/Email-password protocols. + No user passwords or direct login credentials are saved inside the application state. +- **Client Security**: Client-side application calls only the Appwrite browser SDK. No administrative + or server-level API keys are ever shipped or exposed to the client. +- **Database Row Security**: All Appwrite tables have `Row Security` enabled. Users are granted + `create` permissions on the table level, but read, update, and delete actions require explicit + document permissions matching the user's specific ID (`user:{userId}`). +- **LLM API Security**: To avoid key exposure, the client connects to the proxy path `/api/ollama-chat`. + The secret `OLLAMA_API_KEY` is maintained exclusively in secure server-side environment variables. +- **Dependency Auditing**: + - > TODO: Add automated dependency checking (e.g., `npm audit` or Dependabot) in the CI pipeline. +- **Software Licensing**: + - > TODO: Add a standard LICENSE file (e.g., MIT, Apache-2.0) to explicitly detail terms of reuse. + +## Agent Guardrails + +- **Environment File Preservation**: Never edit, modify, or commit variables directly inside + `.env.local` or `.env` templates unless explicitly instructed by the user. +- **Credential Safety**: Never add, write, or hardcode API keys, access secrets, project keys, or + personal tokens into the codebase or configuration files. +- **Safe Directory Boundaries**: Do not add, write, or alter files inside administrative, system, or + auto-generated directories like `.git`, `.gemini`, `dist`, or `node_modules`. +- **Monolithic State Warnings**: `src/App.tsx` contains the core layout and view engine. Exercise + extreme care when making adjustments to prevent breaking the view transitions, auth hooks, or modals. +- **Database Alignment Rules**: Always verify that any changes to DB record structures or types are + mirrored across `src/types.ts`, Appwrite modules (`src/lib/appwriteEntries.ts`, `src/lib/coachChats.ts`), + and the migration runner `scripts/setup-appwrite.mjs`. + +## Extensibility Hooks + +- **Flavour Extensions**: New built-in Red Bull flavours, accent colors, and sugar-free rules can be + easily appended to the `BUILT_IN_FLAVOURS` array in `src/data/flavours.ts`. +- **UI Custom Themes**: Additional visual themes, including Vocaloid, seasonal, or custom branding, + can be integrated by adding definitions to the `APP_THEMES` array in `src/data/themes.ts`. +- **Proxy Endpoint Rerouting**: The Ollama upstream proxy route in `vite.config.ts` and + `api/ollama-chat.js` can be adjusted to point to alternative LLM hosts or local server instances. +- **Configurable Environment Parameters**: + - `VITE_APPWRITE_ENDPOINT` – Base URL for the Appwrite API server. + - `VITE_APPWRITE_PROJECT_ID` – The Appwrite project instance identifier. + - `VITE_APPWRITE_DATABASE_ID` – TablesDB target database identifier. + - `VITE_APPWRITE_COLLECTION_ID` – Table ID containing intake documents. + - `VITE_APPWRITE_CHAT_COLLECTION_ID` – Table ID storing coach chatbot threads. + - `OLLAMA_API_KEY` – Administrative bearer authorization token for Ollama endpoints. + - `OLLAMA_MODEL` – Upstream model identifier (default: `deepseek-v4-pro:cloud`). + +## Further Reading + +- [Appwrite Platform Documentation](file:///Users/ned/Documents/GitHub/Red%20Bull%20Tracking%20System/APPWRITE_SETUP.md) – Detailed guide on database configuration, attributes, indexes, and row permissions. +- [Appwrite Admin Schema Migrations](file:///Users/ned/Documents/GitHub/Red%20Bull%20Tracking%20System/scripts/setup-appwrite.mjs) – Automated table creation and attributes loader script. +- > TODO: Put architecture decision records (ADRs) or technical whitepapers under a dedicated `/docs` directory. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0d4e8e4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,220 @@ +# Project Overview + +Red Bull Intake Tracker is a premium web-based tracking dashboard designed for tracking caffeine +and beverage consumption, with a strong focus on Red Bull products. Built using React, Vite, and +TypeScript, it allows users to record intake (amount, flavour, size, price, timestamp, and location), +monitor daily spending and caffeine limits, view structured trends and streaks, import/export +data via styled Excel or JSON formats, and engage in real-time encrypted-compatible dialogue with an +AI wellness coach powered by a serverless Ollama proxy endpoint. Synchronized dynamically with +Appwrite Cloud databases using secure row-level document permissions, it delivers a highly reactive, +personalized, and privacy-first self-tracking experience. + +## Repository Structure + +- `api/` – Contains serverless backend handlers, including the API gateway proxy for Ollama chat endpoints. +- `dist/` – Contains static HTML, JavaScript, and CSS bundle files output by the production build process. +- `node_modules/` – Stores third-party library dependencies and packages managed by npm. +- `scripts/` – Houses automation scripts, including database schema configuration tools for Appwrite. +- `src/` – Contains client-side React source code, components, utility models, and stylesheets. + - `src/components/` – Reusable UI panel elements, forms, and splash screen wrappers. + - `src/data/` – Static configurations, including theme lists and built-in flavours mapping. + - `src/lib/` – Business logic engines for calculations, file parsers, and Appwrite client connections. + +## Build & Development Commands + +Use the following shell-ready commands to install dependencies, run the application, lint the code, +and manage the cloud database. + +### Dependency Installation +```bash +npm install +``` + +### Local Development Server +Starts a local development server at `http://localhost:5173`. +```bash +npm run dev +``` + +### Production Build & Bundling +Performs TypeScript diagnostic type-checks and compiles the application into the `dist/` directory. +```bash +npm run build +``` + +### Production Preview +Runs a local web server to preview the built production bundle. +```bash +npm run preview +``` + +### Code Linting +Runs ESLint over TypeScript files to identify syntax issues and code style warnings. +```bash +npm run lint +``` + +### Appwrite Cloud Database Setup +Automatically provisions the databases, tables, columns, and indexes on the configured Appwrite instance. +```bash +npm run setup:appwrite +``` + +### Automated Testing +> TODO: Add automated test suite command (e.g., `npm run test` using Vitest or Jest). + +### Application Deployment +> TODO: Add production deployment pipeline command (e.g., Vercel, Netlify, or Docker deploy). + +## Code Style & Conventions + +- **Language**: TypeScript is strictly required for all UI components and logic scripts; JavaScript + is limited to serverless handlers and build scripting. +- **Strict Checks**: TypeScript's `strict` compiler option is enabled; avoid using `any` and ensure + all parameters and return values are explicitly typed. +- **Formatting**: Code should be formatted with 2-space indentation, trailing commas where supported, + and double quotes for JSX/TSX properties. +- **Linting**: Rules are governed by ESLint (`eslint.config.js`), extending the standard TypeScript + and React Hooks rulesets. +- **Naming Conventions**: + - React components and files use PascalCase (e.g., `CoachPanel.tsx`). + - Business logic, utilities, and helper hooks use camelCase (e.g., `appwriteEntries.ts`, `useCoachSession.ts`). + - Constants and static metadata arrays use UPPER_SNAKE_CASE (e.g., `BUILT_IN_FLAVOURS`). +- **Imports**: Prefer explicit ES module imports (`import { ... } from "..."`). Avoid wildcards. +- **Commit Messages**: + - > TODO: Define commit message guidelines and templates (e.g., Conventional Commits). + +## Architecture Notes + +### Flow Architecture Diagram + +```mermaid +graph TD + subgraph Frontend [Vite React Client] + App[App.tsx - Core State & Shell] + Components[Onboarding, CoachPanel, Limits, etc.] + LibMetrics[metrics.ts & userLimits.ts] + LibIO[excel.ts & storage.ts] + LibTheme[themeTokens.ts & themes.ts] + HookCoach[useCoachSession.ts] + end + + subgraph BackendAPI [API & Proxies] + ViteProxy[Vite Dev Server Middleware] + VercelHandler[api/ollama-chat.js Serverless Function] + end + + subgraph External [External Services] + AppwriteCloud[Appwrite Cloud / TablesDB] + OllamaAPI[Ollama Cloud API] + end + + App --> Components + App --> LibMetrics + App --> LibIO + App --> LibTheme + App --> HookCoach + + HookCoach -- "/api/ollama-chat (Local)" --> ViteProxy + HookCoach -- "/api/ollama-chat (Prod)" --> VercelHandler + + ViteProxy -- "Headers Auth" --> OllamaAPI + VercelHandler -- "Headers Auth" --> OllamaAPI + + App --> AppwriteCloud +``` + +### Component Roles & Data Flow + +1. **State Orchestration (`src/App.tsx`)**: + Acts as the monolithic hub of the frontend. It manages user authentication states, currently + selected views, active database operations, modals, onboarding triggers, and theme settings. + +2. **Metrics & Limits (`src/lib/metrics.ts`, `src/lib/userLimits.ts`)**: + Process raw intakes to extract total cans, spendings, caffeine absorption, hydration estimates, + streaks, and coordinate warnings when user limits are breached or bedtime approaches. + +3. **External Data Codecs (`src/lib/excel.ts`, `src/lib/storage.ts`)**: + Implement styled spreadsheet formatting with `exceljs`, data sanity validation, duplicate-aware + import preview engines, and local JSON backup/restore modules. + +4. **Dynamic Styling (`src/lib/themeTokens.ts`, `src/data/themes.ts`)**: + Computes CSS tokens dynamically from a selection of Vocaloid or beverage-themed configurations, + writing variables into the document root for real-time visual styling modifications. + +5. **Cloud Synced State (`src/lib/appwrite.ts`, `src/lib/appwriteEntries.ts`, `src/lib/coachChats.ts`)**: + Establishes client tunnels to Appwrite's serverless TablesDB backend, running row-secured + CRUD actions bound strictly to the current `userId`. + +6. **AI Coach Chatbot (`src/lib/useCoachSession.ts`, `api/ollama-chat.js`)**: + A state-machine custom hook that pipelines user questions, injects historical intake aggregates, + and streams responses from DeepSeek through server-side Ollama proxy tunnels. + +## Testing Strategy + +The repository does not currently feature automated test files. Testing is executed manually. + +### Unit & Integration Testing +- > TODO: Configure unit and integration tests (e.g., Vitest + React Testing Library) to validate + metrics computations, limits checks, and file importing codecs. + +### End-to-End (E2E) Testing +- > TODO: Introduce E2E test suites (e.g., Playwright or Cypress) to cover authentication paths, + entry additions, theme switches, and chatbot conversation loops. + +### Continuous Integration (CI) +- > TODO: Establish a GitHub Actions workflow pipeline to run linters, type checks, and tests on + every branch commit or pull request. + +## Security & Compliance + +- **Authentication**: Delegated entirely to Appwrite's built-in OAuth/Email-password protocols. + No user passwords or direct login credentials are saved inside the application state. +- **Client Security**: Client-side application calls only the Appwrite browser SDK. No administrative + or server-level API keys are ever shipped or exposed to the client. +- **Database Row Security**: All Appwrite tables have `Row Security` enabled. Users are granted + `create` permissions on the table level, but read, update, and delete actions require explicit + document permissions matching the user's specific ID (`user:{userId}`). +- **LLM API Security**: To avoid key exposure, the client connects to the proxy path `/api/ollama-chat`. + The secret `OLLAMA_API_KEY` is maintained exclusively in secure server-side environment variables. +- **Dependency Auditing**: + - > TODO: Add automated dependency checking (e.g., `npm audit` or Dependabot) in the CI pipeline. +- **Software Licensing**: + - > TODO: Add a standard LICENSE file (e.g., MIT, Apache-2.0) to explicitly detail terms of reuse. + +## Agent Guardrails + +- **Environment File Preservation**: Never edit, modify, or commit variables directly inside + `.env.local` or `.env` templates unless explicitly instructed by the user. +- **Credential Safety**: Never add, write, or hardcode API keys, access secrets, project keys, or + personal tokens into the codebase or configuration files. +- **Safe Directory Boundaries**: Do not add, write, or alter files inside administrative, system, or + auto-generated directories like `.git`, `.gemini`, `dist`, or `node_modules`. +- **Monolithic State Warnings**: `src/App.tsx` contains the core layout and view engine. Exercise + extreme care when making adjustments to prevent breaking the view transitions, auth hooks, or modals. +- **Database Alignment Rules**: Always verify that any changes to DB record structures or types are + mirrored across `src/types.ts`, Appwrite modules (`src/lib/appwriteEntries.ts`, `src/lib/coachChats.ts`), + and the migration runner `scripts/setup-appwrite.mjs`. + +## Extensibility Hooks + +- **Flavour Extensions**: New built-in Red Bull flavours, accent colors, and sugar-free rules can be + easily appended to the `BUILT_IN_FLAVOURS` array in `src/data/flavours.ts`. +- **UI Custom Themes**: Additional visual themes, including Vocaloid, seasonal, or custom branding, + can be integrated by adding definitions to the `APP_THEMES` array in `src/data/themes.ts`. +- **Proxy Endpoint Rerouting**: The Ollama upstream proxy route in `vite.config.ts` and + `api/ollama-chat.js` can be adjusted to point to alternative LLM hosts or local server instances. +- **Configurable Environment Parameters**: + - `VITE_APPWRITE_ENDPOINT` – Base URL for the Appwrite API server. + - `VITE_APPWRITE_PROJECT_ID` – The Appwrite project instance identifier. + - `VITE_APPWRITE_DATABASE_ID` – TablesDB target database identifier. + - `VITE_APPWRITE_COLLECTION_ID` – Table ID containing intake documents. + - `VITE_APPWRITE_CHAT_COLLECTION_ID` – Table ID storing coach chatbot threads. + - `OLLAMA_API_KEY` – Administrative bearer authorization token for Ollama endpoints. + - `OLLAMA_MODEL` – Upstream model identifier (default: `deepseek-v4-pro:cloud`). + +## Further Reading + +- [Appwrite Platform Documentation](file:///Users/ned/Documents/GitHub/Red%20Bull%20Tracking%20System/APPWRITE_SETUP.md) – Detailed guide on database configuration, attributes, indexes, and row permissions. +- [Appwrite Admin Schema Migrations](file:///Users/ned/Documents/GitHub/Red%20Bull%20Tracking%20System/scripts/setup-appwrite.mjs) – Automated table creation and attributes loader script. +- > TODO: Put architecture decision records (ADRs) or technical whitepapers under a dedicated `/docs` directory. From ec9ea9d1f9bd63374eb2b74c00c4d0183ca72e03 Mon Sep 17 00:00:00 2001 From: Ned Halksworth Date: Wed, 27 May 2026 14:29:22 +0100 Subject: [PATCH 2/2] feat: integrate barcode scanning functionality and enhance Appwrite setup - Added a new `barcode_products` collection to the Appwrite setup for managing barcode data. - Implemented barcode scanning feature with a dedicated modal for scanning and adding products. - Introduced new components for barcode product preview and management. - Updated the setup script to seed verified barcode products from a JSON file. - Enhanced the application state management to handle barcode-related actions and user interactions. --- APPWRITE_SETUP.md | 3 + package-lock.json | 46 ++ package.json | 1 + scripts/setup-appwrite.mjs | 70 +++ src/App.tsx | 146 +++--- src/components/BarcodeProductPreview.tsx | 60 +++ src/components/BarcodeScannerModal.tsx | 539 +++++++++++++++++++++++ src/data/barcodes.ts | 6 + src/data/flavours.ts | 29 +- src/data/themes.ts | 200 ++++----- src/data/verified-barcodes.json | 475 ++++++++++++++++++++ src/lib/appwrite.ts | 1 + src/lib/appwriteBarcodes.ts | 146 ++++++ src/lib/barcodeLookup.ts | 90 ++++ src/lib/barcodeScanner.ts | 267 +++++++++++ src/lib/useCoachSession.ts | 3 +- src/lib/userBarcodeMappings.ts | 63 +++ src/types.ts | 51 +++ src/vite-env.d.ts | 1 + 19 files changed, 2033 insertions(+), 164 deletions(-) create mode 100644 src/components/BarcodeProductPreview.tsx create mode 100644 src/components/BarcodeScannerModal.tsx create mode 100644 src/data/barcodes.ts create mode 100644 src/data/verified-barcodes.json create mode 100644 src/lib/appwriteBarcodes.ts create mode 100644 src/lib/barcodeLookup.ts create mode 100644 src/lib/barcodeScanner.ts create mode 100644 src/lib/userBarcodeMappings.ts diff --git a/APPWRITE_SETUP.md b/APPWRITE_SETUP.md index b7736e6..ec5e506 100644 --- a/APPWRITE_SETUP.md +++ b/APPWRITE_SETUP.md @@ -37,6 +37,7 @@ Configured defaults: - Database ID: `redbull_tracker` - Collection ID: `intake_entries` - Chat collection ID: `coach_chats` +- Barcode collection ID: `barcode_products` `client.ping()` is called automatically during app boot in `src/App.tsx` through `pingAppwrite()` from `src/lib/appwrite.ts`. @@ -86,6 +87,8 @@ So if the Console asks you to create a **table**, that is the same resource as t The app uses Appwrite's current `TablesDB` SDK methods (`listRows`, `createRow`, `updateRow`, `deleteRow`). The env var remains named `VITE_APPWRITE_COLLECTION_ID` for compatibility with the first setup pass, but its value should be your table ID. +The barcode scanner uses a separate `barcode_products` table by default. Verified Red Bull barcode rows are seeded by `scripts/setup-appwrite.mjs` using `APPWRITE_API_KEY`; browser code can only read verified rows and create/update the current user's own mappings with row-level permissions. + Create a database with ID: ```text diff --git a/package-lock.json b/package-lock.json index 4f3b2f5..40f071d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@vitejs/plugin-react": "^4.3.4", + "@zxing/browser": "^0.2.0", "appwrite": "^25.0.0", "exceljs": "^4.4.0", "framer-motion": "^11.18.2", @@ -1898,6 +1899,41 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@zxing/browser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.2.0.tgz", + "integrity": "sha512-+ORhrLva0vm6ck74NDCmvYNW3XLoAG81Mu90qfcssN1PBKJjQadxZGeMCcIk+BdJbD/zEAjjHDXOwEK1QCmRtw==", + "license": "MIT", + "optionalDependencies": { + "@zxing/text-encoding": "^0.9.0" + }, + "peerDependencies": { + "@zxing/library": "^0.22.0" + } + }, + "node_modules/@zxing/library": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.22.0.tgz", + "integrity": "sha512-BmInervZV7NwaZWX1LW64sZ4Lh4wxXYFZwGmj98ArPOkRXCtO9b8Gog0Xyh82dsYYGOeRxX+aAhLSq+hQ2XLZQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "ts-custom-error": "^3.3.1" + }, + "engines": { + "node": ">= 24.0.0" + }, + "optionalDependencies": { + "@zxing/text-encoding": "~0.9.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -5079,6 +5115,16 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-custom-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", + "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", diff --git a/package.json b/package.json index 7fec1a0..d877f09 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@vitejs/plugin-react": "^4.3.4", + "@zxing/browser": "^0.2.0", "appwrite": "^25.0.0", "exceljs": "^4.4.0", "framer-motion": "^11.18.2", diff --git a/scripts/setup-appwrite.mjs b/scripts/setup-appwrite.mjs index b04777a..036e48c 100644 --- a/scripts/setup-appwrite.mjs +++ b/scripts/setup-appwrite.mjs @@ -1,6 +1,7 @@ /* global console, fetch, process, setTimeout */ import { existsSync, readFileSync } from "node:fs"; +import { URL } from "node:url"; const env = loadEnvFiles([".env", ".env.local"]); @@ -9,7 +10,11 @@ const projectId = readEnv("VITE_APPWRITE_PROJECT_ID", "6a0752ee001fb2ef7138"); const databaseId = readEnv("VITE_APPWRITE_DATABASE_ID", "redbull_tracker"); const intakeTableId = readEnv("VITE_APPWRITE_COLLECTION_ID", "intake_entries"); const chatTableId = readEnv("VITE_APPWRITE_CHAT_COLLECTION_ID", "coach_chats"); +const barcodeTableId = readEnv("VITE_APPWRITE_BARCODE_COLLECTION_ID", "barcode_products"); const apiKey = readEnv("APPWRITE_API_KEY", ""); +const verifiedBarcodeProducts = JSON.parse( + readFileSync(new URL("../src/data/verified-barcodes.json", import.meta.url), "utf8"), +); if (!apiKey) { throw new Error("APPWRITE_API_KEY missing. Add a server/admin Appwrite key to .env.local, without VITE_."); @@ -59,6 +64,34 @@ await retireLegacyChatColumns(chatTableId, [ "version", ]); await waitForColumns(chatTableId, ["userId", "title", "messages", "updatedAt"]); +await ensureTable({ + tableId: barcodeTableId, + name: "Barcode products", + // Schema notes: + // - scope="verified" rows are seeded by this admin script and readable by signed-in users. + // - scope="user" rows are created by the browser SDK with per-user row permissions. + columns: [ + { kind: "string", key: "scope", size: 16, required: true }, + { kind: "string", key: "ownerUserId", size: 64, required: false }, + { kind: "string", key: "barcode", size: 32, required: true }, + { kind: "string", key: "flavourName", size: 128, required: true }, + { kind: "integer", key: "sizeMl", required: true }, + { kind: "float", key: "pricePerCan", required: true }, + { kind: "boolean", key: "sugarFree", required: true }, + { kind: "float", key: "caffeineMgPerCan", required: false }, + { kind: "string", key: "verifiedBy", size: 512, required: false }, + { kind: "string", key: "sourceName", size: 512, required: false }, + { kind: "string", key: "sourceUrl", size: 2048, required: false }, + { kind: "string", key: "variant", size: 64, required: false }, + { kind: "string", key: "notes", size: 2000, required: false }, + ], + indexes: [ + { key: "barcode", type: "key", columns: ["barcode"], orders: ["ASC"], lengths: [32] }, + { key: "scope_barcode", type: "key", columns: ["scope", "barcode"], orders: ["ASC", "ASC"], lengths: [16, 32] }, + { key: "user_barcode", type: "key", columns: ["ownerUserId", "barcode"], orders: ["ASC", "ASC"], lengths: [64, 32] }, + ], +}); +await seedVerifiedBarcodeProducts(barcodeTableId, verifiedBarcodeProducts); console.log("Appwrite database and tables ready."); @@ -156,6 +189,43 @@ async function ensureIndex(tableId, index) { console.log(`Index ${tableId}.${index.key} created.`); } +async function seedVerifiedBarcodeProducts(tableId, products) { + for (const [barcode, product] of Object.entries(products)) { + const rowId = `verified_${barcode}`; + const data = { + scope: "verified", + ownerUserId: "", + barcode, + flavourName: product.flavourName, + sizeMl: product.sizeMl, + pricePerCan: product.pricePerCan, + sugarFree: Boolean(product.sugarFree), + caffeineMgPerCan: product.caffeineMgPerCan, + verifiedBy: product.verifiedBy ?? "", + sourceName: product.sourceName ?? "", + sourceUrl: product.sourceUrl ?? "", + variant: product.variant ?? "", + notes: product.notes ?? "", + }; + const path = `/tablesdb/${databaseId}/tables/${tableId}/rows/${rowId}`; + const existing = await request("GET", path, undefined, [200, 404]); + + if (existing.status === 404) { + await request( + "POST", + `/tablesdb/${databaseId}/tables/${tableId}/rows`, + { rowId, data, permissions: ['read("users")'] }, + [201], + ); + console.log(`Verified barcode ${barcode} seeded.`); + continue; + } + + await request("PUT", path, { data, permissions: ['read("users")'] }, [200]); + console.log(`Verified barcode ${barcode} updated.`); + } +} + async function waitForColumns(tableId, keys) { const pending = new Set(keys); for (let attempt = 0; attempt < 30 && pending.size; attempt += 1) { diff --git a/src/App.tsx b/src/App.tsx index fcfc88c..f7d9035 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { Activity, AlertTriangle, CalendarDays, + Camera, ChevronRight, Cloud, Command, @@ -82,6 +83,7 @@ import { updateEntry, } from "./lib/appwriteEntries"; import { CoachPanel } from "./components/CoachPanel"; +import { BarcodeScannerModal } from "./components/BarcodeScannerModal"; import { DailyLimitsCard } from "./components/DailyLimitsCard"; import { LimitsSettingsForm } from "./components/LimitsSettingsForm"; import { OnboardingScreen } from "./components/OnboardingScreen"; @@ -191,7 +193,9 @@ function App() { const [filters, setFilters] = useState(DEFAULT_FILTERS); const [activeView, setActiveView] = useState("overview"); const [isEntryModalOpen, setIsEntryModalOpen] = useState(false); + const [entryInitialDraft, setEntryInitialDraft] = useState(null); const [editingEntry, setEditingEntry] = useState(null); + const [isBarcodeScannerOpen, setIsBarcodeScannerOpen] = useState(false); const [isResetOpen, setIsResetOpen] = useState(false); const [notice, setNotice] = useState("Appwrite session pending."); const [dataLoading, setDataLoading] = useState(false); @@ -390,9 +394,14 @@ function App() { function openNewEntry() { setEditingEntry(null); + setEntryInitialDraft(null); setIsEntryModalOpen(true); } + function openBarcodeScanner() { + setIsBarcodeScannerOpen(true); + } + async function saveUserLimits(next: UserLimits) { if (!user) return; setActionLoading("save-limits"); @@ -451,6 +460,7 @@ function App() { ); setNotice(editing ? "Entry updated in Appwrite." : "Entry saved to Appwrite."); setEditingEntry(null); + setEntryInitialDraft(null); setIsEntryModalOpen(false); } catch (error) { setDataError(appwriteErrorMessage(error)); @@ -478,6 +488,18 @@ function App() { requestEntrySave(draft, editingEntry?.id); } + function addBarcodeDraft(draft: EntryDraft) { + setIsBarcodeScannerOpen(false); + requestEntrySave(draft); + } + + function editBarcodeDraft(draft: EntryDraft) { + setIsBarcodeScannerOpen(false); + setEditingEntry(null); + setEntryInitialDraft(draft); + setIsEntryModalOpen(true); + } + async function quickAdd(item: (typeof QUICK_ADDS)[number]) { if (!user) return; const meta = flavourMeta(item.flavour); @@ -682,6 +704,7 @@ function App() { setupStatus={setupStatus} user={user} onAdd={openNewEntry} + onScan={openBarcodeScanner} onChange={setActiveView} onOpenSettings={() => setActiveView("settings")} /> @@ -693,6 +716,7 @@ function App() { activeView={activeView} actionLoading={actionLoading} onAdd={openNewEntry} + onScan={openBarcodeScanner} /> @@ -721,6 +745,7 @@ function App() { coachSession={coachSession} onQuickAdd={(item) => void quickAdd(item)} onAdd={openNewEntry} + onScan={openBarcodeScanner} onOpenCoach={(prompt) => { if (prompt) coachSession.queuePrompt(prompt); setActiveView("coach"); @@ -801,6 +826,7 @@ function App() { { setIsEntryModalOpen(false); setEditingEntry(null); + setEntryInitialDraft(null); }} onSave={(draft) => void saveEntry(draft)} /> + setIsBarcodeScannerOpen(false)} + onEditBeforeAdding={editBarcodeDraft} + /> + -