Skip to content

briandenicola/watch-tracker-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WatchTracker

Note: This application is 100% vibe coded. It's exclusively for me to learn and experiment with GitHub Copilot CLI.

WatchTracker is a full-stack web application for cataloging and managing your personal watch collection. Track details like brand, model, movement type, band type, purchase info, and images — with optional AI-powered watch analysis via the Anthropic API. Every watch is scoped to your authenticated account using JWT-based authentication.

It also includes a wish list for tracking watches you want to buy, a stats dashboard with a GitHub-style wear heatmap, and wear logging to track each time you wear a watch.

On first launch, a setup wizard walks you through creating an admin account and configuring application settings.

Architecture

Layer Tech Path
Backend .NET 10 Web API, EF Core, SQLite src/api/
Frontend React 19, TypeScript, Vite (PWA) src/web/

The React SPA communicates with the .NET API exclusively via REST (/api/*). In production the API serves the SPA as static files from a single container, so no separate web server is needed.

The frontend is a Progressive Web App (PWA) and can be installed on iOS (Safari → Share → Add to Home Screen), Android, and desktop browsers for a native app-like experience with offline caching. When running as a standalone PWA on iOS, the navigation bar collapses into a hamburger menu to avoid the notch and status bar area.

Prerequisites

Getting Started

Clone the repository and start the development servers:

git clone <repo-url> && cd watch-tracker-app
task run        # starts both API and frontend in parallel

The API runs on http://localhost:5062 and the Vite dev server on http://localhost:5173. You can also start them individually:

task run-api    # API only
task run-web    # frontend only

When the app launches for the first time, the setup wizard at /setup will guide you through creating an admin account and optionally configuring the Anthropic API key for AI watch analysis.

Task Commands

Command Description
task init Generate a .env file with a random JWT key
task run Run API and frontend in parallel
task run-api Run the .NET API server
task run-web Run the Vite dev server
task build Build both API and frontend
task build-api Build the .NET API project
task build-web Build the React frontend
task docker-build Build the Docker container image
task docker-run Run the Docker container locally
task db Create and apply EF Core migrations

CI/CD

A GitHub Actions workflow (.github/workflows/docker-publish.yml) builds and pushes the Docker image to Docker Hub:

  • Triggers — Push to main branch and manual dispatch
  • Tags — Full commit SHA, short commit SHA, and latest (on main)
  • Caching — Uses GitHub Actions cache for Docker layer caching

Required Secrets

Secret Description
DOCKERHUB_USERNAME Your Docker Hub username
DOCKERHUB_TOKEN A Docker Hub access token

Deployment

The application ships as a single Docker container that serves both the API and the React SPA. The pre-built image is hosted on GitHub Container Registry.

Configuration

Generate a .env file with a random JWT signing key:

task init

This creates a .env file with a cryptographically random JWT_KEY. Edit the file to optionally add your Anthropic API key:

JWT_KEY=<auto-generated>
ANTHROPIC_API_KEY=          # optional — enables AI watch analysis
ALLOWED_ORIGINS=*           # optional — restrict CORS origins (semicolon-separated, or * for any)
LOG_LEVEL=Information       # optional — initial log level (Trace, Debug, Information, Warning, Error, Critical, None)

Running with Docker Compose

docker compose up

Running with Task

task docker-build           # build the image from source
task docker-run             # run the container (reads .env automatically)

Running with Docker directly

docker run -p 8080:8080 -d \
  -e Jwt__Key=YourSecretKeyHere \
  -e Jwt__Issuer=WatchTracker \
  -e Jwt__Audience=WatchTracker \
  -e AllowedOrigins=* \
  -v watch-tracker-data:/app/data \
  -v watch-tracker-uploads:/app/uploads \
  ghcr.io/briandenicola/watch-tracker-app:latest

The application will be available on http://localhost:8080. On first launch, the setup wizard will guide you through creating an admin account.

Deploying Behind a Reverse Proxy

The .NET backend handles X-Forwarded-For and X-Forwarded-Proto headers automatically. The standalone web container (src/web/Dockerfile) uses a Node.js server that proxies /api/ and /uploads/ to the backend — no nginx required.

Set AllowedOrigins to your public domain(s) or * if the proxy is trusted:

# Single origin
-e AllowedOrigins=https://watches.example.com

# Multiple origins (semicolon-separated)
-e AllowedOrigins="https://watches.example.com;https://api.example.com"

The web container accepts an API_URL environment variable (default http://localhost:8080) to locate the backend:

services:
  web:
    build: src/web
    ports:
      - "3000:3000"
    environment:
      - API_URL=http://api:8080
      - PORT=3000
  api:
    build: src/api
    # ...

Features

Collection Views

The main collection page supports two view modes:

  • Gallery View — Card grid showing each watch's cover image with brand and model. Supports infinite scroll (loads 12 at a time), filtering by brand and band type, and sorting by date added, brand, or last worn date (ascending/descending).
  • Table View — Sortable table with columns for Brand, Model, Size, Type, Date Added, Last Worn, and Worn Count. All columns are sortable. On mobile screens (≤600px), the table collapses to Brand, Model, and Date Added for readability.

Toggle between views using the ▦/☰ buttons in the toolbar. The Brand field on the Add/Edit Watch forms includes an autocomplete dropdown populated from existing brands in your collection.

Wish List

Track watches you'd like to buy without cluttering your main collection:

  • Add to Wish List — Simplified form collecting brand, model, estimated price, product page URL, and an image URL. The image is downloaded from the URL and stored locally.
  • Wish List Gallery — Toggle the "Wish List" button in the toolbar to view your wish list. Cards show the watch image (click to edit) and the brand/model as a link to the product page.
  • Edit Wish List Item — Update details, replace the image, delete the item, or mark it as Purchased which redirects to the Add Watch page with the brand and model pre-filled.

Wish list items are stored in the same database table as watches but are hidden from the main collection by default.

Wear Tracking & Stats

Each time you click Wore Today on a watch detail page, an individual wear event is logged (in addition to the aggregate counters). The Stats page (accessible from the navigation bar) shows:

  • Summary cards — Total wears and unique watches worn
  • Wear Heatmap — A GitHub-style 365-day heatmap showing wear frequency per day, with 4 intensity levels and dark/light theme support
  • Wear Timeline — Chronological list of the 30 most recent wear events, each linking to the watch detail page

Watch Details

Each watch can store:

  • Core fields — Brand, model, movement type, case size, band type, purchase date/price
  • Extended properties — Crystal type, case shape, crown type, calendar type, country of origin, water resistance, lug width, dial color, bezel type, power reserve, serial/reference number
  • Link — A URL with customizable display text (defaults to "Product Page"), shown as a chip on the detail page
  • Notes — Markdown-supported notes field, displayed in a scrollable container within the Additional Details accordion
  • Images — Multiple image uploads per watch with the ability to choose a cover image for the gallery view

AI Watch Analysis

Upload photos of a watch and click 🤖 Analyze with AI to get an AI-powered analysis via the Anthropic API. The analysis is returned in a modal popup rendered as Markdown. If accepted, the analysis is appended to the watch's notes.

Cover Image Selection

When editing a watch with multiple images, a Gallery Image picker lets you choose which image appears as the cover in the gallery view.

User Preferences

All authenticated users can access Settings (click your avatar in the navigation bar) to configure:

  • My Account — Change your display username and profile photo. Your email address is shown but not editable.
  • Change Password — Update your account password.
  • Theme — Switch between light and dark mode. Defaults to the OS preference and persists in the browser.
  • Default View — Choose between gallery and table as the default collection view.
  • Time Zone — Select your time zone for date/time display. Defaults to your browser's time zone.

Settings are displayed in a modal popup rather than a separate page.

Admin Settings

Admins can manage application-wide settings under Admin → Settings, organized into grouped sections:

  • My Account — Change your display username. Your email address is shown but not editable.
  • AI Configuration — View the masked Anthropic API key (first 10 characters only) and reset it with a new value. Customize the AI analysis prompt.
  • Security — Max failed login attempts and lockout duration.
  • Logging — Runtime log level (Trace through None). Changes take effect immediately without a restart.

Environment Variables

Variable Default Description
JWT_KEY (required) JWT signing key (openssl rand -base64 48)
ALLOWED_ORIGINS * CORS allowed origins (* or semicolon-separated URLs)
LOG_LEVEL Information Initial log level (Trace, Debug, Information, Warning, Error, Critical, None)
ASPNETCORE_FORWARDEDHEADERS_ENABLED true Enable forwarded headers for reverse proxy support

Project Structure

watch-tracker-app/
├── .github/
│   └── workflows/
│       ├── codeql-analysis.yml   # CodeQL security scanning
│       └── docker-publish.yml    # Build & push to Docker Hub
├── src/
│   ├── api/                      # .NET 10 Web API
│   │   ├── Controllers/          # API endpoints
│   │   ├── DTOs/                 # Data transfer objects
│   │   ├── Models/               # EF Core entities
│   │   ├── Services/             # Business logic
│   │   ├── Data/                 # DbContext & configuration
│   │   ├── Migrations/           # EF Core migrations
│   │   └── Program.cs            # App entry point
│   └── web/                      # React SPA
│       ├── src/
│       │   ├── api/              # API client & resource functions
│       │   ├── components/       # Reusable components (WatchCard, WatchForm, etc.)
│       │   ├── context/          # React context (auth, preferences)
│       │   ├── pages/            # Page components
│       │   ├── types/            # TypeScript type definitions
│       │   └── utils/            # Utility functions (gravatar, etc.)
│       ├── public/               # PWA icons & static assets
│       ├── index.html
│       └── vite.config.ts
├── Dockerfile                    # Multi-stage build (web + API)
├── Taskfile.yml                  # Task runner configuration
├── docker-compose.yaml           # Container orchestration
└── README.md

Backlog

Future feature ideas for the app:

  • Maintenance Tracker — Log service history, battery replacements, and strap changes. Set reminders for the next scheduled service.
  • Watch Comparison — Side-by-side spec comparison of any two watches in your collection.
  • Collection Statistics — Dashboard with total collection value, most-worn watch, brand breakdown charts, average price, and wearing streaks.
  • Collection Timeline — Visual timeline of when each watch was acquired, showing how the collection grew over time.
  • Export / Import — Export your collection as CSV or JSON. Import watches from a spreadsheet.

License

This project is licensed under the MIT License.

About

A vibe coded application to track my watch collection

Topics

Resources

Stars

Watchers

Forks

Contributors