DEPLOYMENT COMMAND
npm run pages:build && wrangler deploy
United Tattoo
Professional tattoo studio platform built on Cloudflare's edge network
Lightning-fast • Artist-first • Calendar-integrated
View Live Site »
Quick Start
·
Report Bug
·
Request Feature
Table of Contents
About The Project
United Tattoo is a comprehensive tattoo studio management platform designed for United Tattoo, a professional tattoo studio in Fountain, Colorado. This platform seamlessly integrates artist portfolios, appointment booking, flash tattoo marketplace, and real-time calendar synchronization—all running on Cloudflare's global edge network for exceptional performance.
Key Features
Artist Management
Flash Tattoo Marketplace
Smart Booking System
|
CalDAV/Nextcloud Integration
Nextcloud OAuth Authentication
Comprehensive Dashboards
|
Why United Tattoo?
Edge-First Architecture Built on Cloudflare Workers for global performance with <100ms response times worldwide.
Nextcloud-First Integration Seamless authentication and calendar sync with existing artist infrastructure—no duplicate account management.
Zero-ORM Overhead Direct Cloudflare D1 integration via bindings for maximum performance.
Bundle Size Enforced CI/CD enforces 3MB static budget to ensure lightning-fast page loads.
Test Coverage Comprehensive Vitest test suite with coverage tracking.
Tech Stack
Core Framework
Cloudflare Infrastructure
UI & Styling
Database & Storage
Authentication & Integration
Testing & Quality
Complete Dependency List
Core Dependencies:
- Next.js 14.2.33 (App Router)
- React 18
- TypeScript 5
- OpenNext for Cloudflare 1.8.2
UI Components:
- 30+ Radix UI components (via ShadCN)
- Framer Motion 12.23.24 (animations)
- Lenis (smooth scrolling)
- next-themes 0.4.6 (dark mode)
Data & State:
- TanStack React Query 5.89.0
- Zod 3.25.67 (validation)
- React Hook Form 7.60.0
Calendar & CalDAV:
- tsdav 2.1.5 (CalDAV client)
- react-big-calendar 1.19.4
- ical.js 1.5.0
- date-fns
File Processing:
- Sharp 0.34.4 (image processing)
- heic-convert 2.1.0 (HEIC → JPEG)
- AWS SDK for S3 3.890.0 (R2 compatible)
Testing:
- Vitest 3.2.4
- React Testing Library 16.3.0
- @vitest/coverage-v8
Architecture
graph TB
subgraph "Client Layer"
Browser[Browser]
end
subgraph "Edge Layer - Cloudflare Workers"
NextJS[Next.js 14<br/>App Router]
OpenNext[OpenNext<br/>Adapter]
end
subgraph "Cloudflare Services"
D1[(D1 Database<br/>SQLite)]
R2[R2 Storage<br/>Files & Images]
end
subgraph "External Services"
Nextcloud[Nextcloud<br/>OAuth + CalDAV]
end
Browser --> NextJS
NextJS --> OpenNext
OpenNext --> D1
OpenNext --> R2
OpenNext <--> Nextcloud
style Browser fill:#667eea,color:#fff
style NextJS fill:#000,color:#fff
style OpenNext fill:#f38020,color:#fff
style D1 fill:#f38020,color:#fff
style R2 fill:#f38020,color:#fff
style Nextcloud fill:#0082c9,color:#fff
System Flow
Authentication Flow
User Request → NextAuth.js
↓
├─ Nextcloud OAuth (Primary)
│ ├─ Check user groups (via OCS API)
│ ├─ Assign role based on group
│ │ ├─ "admins" → SUPER_ADMIN
│ │ ├─ "shop_admins" → SHOP_ADMIN
│ │ ├─ "artists" → ARTIST (auto-create profile)
│ │ └─ Other → Deny access
│ └─ Create/update user in D1
│
├─ Credentials (Fallback - Admin Only)
│ ├─ Query parameter: ?admin=true
│ ├─ Email/password validation
│ └─ Dev mode: Auto-create SUPER_ADMIN
│
└─ Session Created (JWT)
CalDAV Sync Flow
Appointment Created/Updated
↓
syncAppointmentToCalendar()
↓
├─ Get artist's Nextcloud calendar config
├─ Connect to CalDAV server
├─ Create/update VEVENT
├─ Store calendar_event_uid in D1
└─ Log sync operation
Background Sync (Periodic)
↓
pullCalendarEventsToDatabase()
↓
├─ Fetch events from CalDAV
├─ Compare with D1 appointments
├─ Update changed appointments
├─ Import external events
└─ Log sync results
Database Schema
Core Tables:
| Table | Description | Key Fields |
|---|---|---|
users |
Authentication & profiles | id, email, name, role, nextcloud_user_id |
artists |
Artist profiles | id, user_id, slug, bio, specialties (JSON), hourly_rate |
portfolio_images |
Artist work gallery | id, artist_id, image_url, tags (JSON), order, is_public |
flash_items |
Pre-drawn designs | id, artist_id, title, price, is_available |
appointments |
Booking system | id, artist_id, client_id, status, start_time, end_time, deposit |
availability |
Artist schedules | id, artist_id, day_of_week, start_time, end_time |
artist_calendars |
CalDAV configuration | id, artist_id, calendar_url, username, password |
calendar_sync_logs |
Sync monitoring | id, artist_id, operation, status, details |
site_settings |
Global config | key, value, type |
file_uploads |
R2 file tracking | id, user_id, file_url, file_size, mime_type |
Indexes: Optimized for artist lookups, appointment queries, and calendar sync operations.
Project Structure
united-tattoo/
├── app/ # Next.js App Router
│ ├── (public)/ # Public pages (no auth)
│ │ ├── artists/ # Artist profiles & portfolios
│ │ ├── book/ # Booking pages
│ │ └── page.tsx # Homepage
│ ├── admin/ # Admin dashboard (SUPER_ADMIN, SHOP_ADMIN)
│ ├── artist-dashboard/ # Artist self-service (ARTIST)
│ ├── api/ # API routes
│ │ ├── artists/ # Artist CRUD
│ │ ├── appointments/ # Booking endpoints
│ │ ├── caldav/ # Calendar sync
│ │ ├── flash/ # Flash items
│ │ └── upload/ # R2 file uploads
│ └── auth/ # NextAuth pages
├── lib/ # Core logic
│ ├── db.ts # D1 database functions
│ ├── auth.ts # NextAuth config + helpers
│ ├── caldav-client.ts # CalDAV integration
│ ├── calendar-sync.ts # Sync logic
│ ├── nextcloud-client.ts # Nextcloud API client
│ ├── r2-upload.ts # R2 file handling
│ └── env.ts # Environment validation
├── components/ # React components
│ ├── ui/ # ShadCN components
│ └── ... # Feature components
├── sql/ # Database
│ ├── schema.sql # Main schema
│ └── migrations/ # Migration files
├── docs/ # Documentation
├── .gitea/workflows/ # CI/CD pipelines
└── wrangler.toml # Cloudflare config
Getting Started
Prerequisites
Before starting, ensure you have:
- Cloudflare Account with access to Workers, D1, R2, and Pages
- Nextcloud Instance with admin access for OAuth app creation
- Node.js 18+ and npm installed
- Wrangler CLI version 3+
Install Wrangler:
npm install -g wrangler
Cloudflare Resources Required:
- Workers & Pages: For hosting the application
- D1 Database: SQLite database (named
united-tattoo) - R2 Buckets: File storage (
united-tattoo,united-tattoo-inc-cache)
Installation
-
Clone the repository
git clone https://git.biohazardvfx.com/nicholai/united-tattoo.git cd united-tattoo -
Install dependencies
npm install -
Configure environment variables
cp .env.example .env.local # Edit .env.local with your credentials (see Environment Variables section) -
Set up Cloudflare D1 database
# Create D1 database (if not exists) wrangler d1 create united-tattoo # Apply schema to local D1 npm run db:migrate:local # Apply schema to preview/production npm run db:migrate:latest:preview -
Configure Nextcloud OAuth
See
docs/NEXTCLOUD-OAUTH-SETUP.mdfor detailed instructions on:- Creating OAuth application in Nextcloud
- Configuring group-based role assignment
- Setting up service account for CalDAV
-
Run locally
# Next.js dev server (port 3000) npm run dev # OR with Cloudflare Workers simulation npm run dev:wrangler -
Access the application
- Local:
http://localhost:3000 - Sign in:
http://localhost:3000/auth/signin - Admin signin:
http://localhost:3000/auth/signin?admin=true
- Local:
Environment Variables
Required Variables
| Variable | Description | Example |
|---|---|---|
| Database | ||
DATABASE_URL |
PostgreSQL URL (legacy, using D1 via bindings) | postgresql://... |
DIRECT_URL |
Direct database connection (optional) | postgresql://... |
| Authentication | ||
NEXTAUTH_URL |
Application URL | https://united-tattoos.com |
NEXTAUTH_SECRET |
NextAuth secret key | Generate with openssl rand -base64 32 |
| File Storage (Cloudflare R2) | ||
AWS_ACCESS_KEY_ID |
R2 access key ID | From Cloudflare dashboard |
AWS_SECRET_ACCESS_KEY |
R2 secret access key | From Cloudflare dashboard |
AWS_REGION |
Region (any valid AWS region) | us-east-1 |
AWS_BUCKET_NAME |
R2 bucket name | united-tattoo |
AWS_ENDPOINT_URL |
R2 endpoint URL | https://<account-id>.r2.cloudflarestorage.com |
| Nextcloud OAuth | ||
NEXTCLOUD_BASE_URL |
Nextcloud instance URL | https://portal.united-tattoos.com |
NEXTCLOUD_OAUTH_CLIENT_ID |
OAuth app client ID | From Nextcloud admin |
NEXTCLOUD_OAUTH_CLIENT_SECRET |
OAuth app client secret | From Nextcloud admin |
NEXTCLOUD_ARTISTS_GROUP |
Group name for artists | artists |
NEXTCLOUD_ADMINS_GROUP |
Group name for shop admins | shop_admins |
Optional Variables
| Variable | Description | Default |
|---|---|---|
| CalDAV (Calendar Sync) | ||
NEXTCLOUD_USERNAME |
Service account username | — |
NEXTCLOUD_PASSWORD |
Service account password/app password | — |
NEXTCLOUD_CALENDAR_BASE_PATH |
CalDAV base path | /remote.php/dav/calendars |
| OAuth Providers (Deprecated) | ||
GOOGLE_CLIENT_ID |
Google OAuth client ID | — |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret | — |
GITHUB_CLIENT_ID |
GitHub OAuth client ID | — |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret | — |
| Migration | ||
MIGRATE_TOKEN |
Token for public migration endpoint | Generate random string |
| Analytics | ||
VERCEL_ANALYTICS_ID |
Vercel Analytics ID | — |
.env.local for local development and configure production variables in Cloudflare dashboard under Settings → Environment Variables.
Development
Common Commands
| Command | Description |
|---|---|
| Development | |
npm run dev |
Start Next.js dev server (port 3000) |
npm run dev:wrangler |
Build and preview with OpenNext/Cloudflare |
| Testing | |
npm run test |
Run Vitest in watch mode |
npm run test:ui |
Run Vitest with interactive UI |
npm run test:run |
Run tests once (CI mode) |
npm run test:coverage |
Run tests with coverage report |
| Build & Deployment | |
npm run pages:build |
Build with OpenNext for Cloudflare |
npm run build |
Standard Next.js build (standalone) |
npm run preview |
Preview OpenNext build locally |
npm run deploy |
Deploy to Cloudflare Pages |
| Code Quality | |
npm run ci:lint |
Run ESLint |
npm run ci:typecheck |
TypeScript type checking (noEmit) |
npm run ci:test |
Run tests with coverage (CI) |
npm run ci:build |
Build for production (CI) |
npm run ci:budgets |
Check bundle size budgets |
| Formatting | |
npm run lint |
Run ESLint |
npm run format |
Format code with Prettier |
npm run format:check |
Check formatting without changing files |
Database Management
Migration Commands
Local Database:
# Apply schema to local D1
npm run db:migrate:local
# View tables in local D1
npm run db:studio:local
# Backup local database
npm run db:backup:local
Preview Environment (default):
# Apply schema to preview D1
npm run db:migrate
# Apply all migrations from sql/migrations/
npm run db:migrate:latest:preview
# View tables in preview D1
npm run db:studio
# Backup preview database
npm run db:backup
Production Environment:
# Apply specific migration to production
npm run db:migrate:up:prod
# Apply all migrations to production
npm run db:migrate:latest:prod
Direct Wrangler Commands:
# Execute SQL query on local D1
wrangler d1 execute united-tattoo --local --command="SELECT * FROM artists"
# Apply schema file
wrangler d1 execute united-tattoo --file=./sql/schema.sql
# Execute with preview (remote)
wrangler d1 execute united-tattoo --command="SELECT * FROM users"
Creating New Migrations
-
Create migration file in
sql/migrations/with format:YYYYMMDD_NNNN_description.sqlExample:
20250130_0001_add_flash_items_table.sql -
Write your SQL migration:
-- Add flash_items table CREATE TABLE IF NOT EXISTS flash_items ( id TEXT PRIMARY KEY, artist_id TEXT NOT NULL, title TEXT NOT NULL, price REAL NOT NULL, is_available INTEGER DEFAULT 1, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE ); -
Test locally:
npm run db:migrate:local -
Apply to preview:
npm run db:migrate:latest:preview -
Apply to production (when ready):
npm run db:migrate:latest:prod
Deployment
Production URL: https://united-tattoos.com
Deployment Process
npm run pages:build && wrangler deploy
Step-by-Step:
-
Build with OpenNext
npm run pages:buildThis creates a Cloudflare-compatible build in
.vercel/output/static -
Deploy to Cloudflare Pages
wrangler pages deploy .vercel/output/static -
Verify deployment
- Check Cloudflare dashboard: Workers & Pages → united-tattoo
- Visit production URL: https://united-tattoos.com
CI/CD Pipeline
The project uses Gitea workflows for automated CI/CD:
Workflows:
-
ci.yaml- Main CI pipeline- ESLint
- TypeScript type checking
- Vitest tests with coverage
- Production build
- Bundle size budgets
-
deploy.yaml- Automated deployments- Triggers on push to
mainbranch - Builds and deploys to Cloudflare
- Triggers on push to
-
security.yaml- Security audits- npm audit
- Dependency vulnerability scanning
-
performance.yaml- Performance checks- Bundle size analysis
- Preview smoke tests
Bundle Size Budgets:
- Total static assets: 3MB max
- Individual assets: 1.5MB max
Enforced by scripts/budgets.mjs in CI.
Docker Support
Docker Deployment (Alternative)
The project includes a Dockerfile for self-hosting:
# Build image
docker build -t united-tattoo:latest .
# Run container
docker run --rm -p 3000:3000 \
-e PORT=3000 \
-e NEXTAUTH_URL=http://localhost:3000 \
-e NEXTAUTH_SECRET=your-secret \
# ... other env vars
united-tattoo:latest
Note: Docker deployment bypasses Cloudflare Workers and uses Next.js standalone mode. For production, Cloudflare deployment is recommended.
Documentation
Comprehensive documentation is available in the docs/ directory:
Key Documentation
| Document | Description |
|---|---|
| NEXTCLOUD-OAUTH-SETUP.md | Complete guide to setting up Nextcloud OAuth and group-based authentication |
| CALDAV-SETUP.md | Instructions for configuring CalDAV calendar synchronization |
| CI-CD-PIPELINE.md | Detailed CI/CD pipeline documentation and troubleshooting |
| BOOKING-WORKFLOW-FINAL-PLAN.md | Complete booking system architecture and workflow |
Additional Documentation
View All Documentation Files
Authentication & Integration:
Booking & Calendar:
BOOKING-WORKFLOW-FINAL-PLAN.mdBOOKING-WORKFLOW-REVISED-PLAN.mdBOOKING-WORKFLOW-RISKS.mdCALDAV-SETUP.md
CI/CD & DevOps:
Performance & SEO:
Project Management:
AI Development Guide
The project includes CLAUDE.md with comprehensive instructions for AI assistants (like Claude Code) working with this codebase, including:
- Complete architecture overview
- Common commands reference
- Database layer patterns
- Authentication flows
- CalDAV integration details
- Development best practices
Roadmap
Completed Features
- Artist portfolio system with galleries
- Nextcloud OAuth with auto-provisioning
- CalDAV bidirectional sync
- Flash tattoo marketplace
- Admin dashboard with analytics
- Artist self-service dashboard
- Appointment booking system
- R2 file storage integration
- Role-based access control
- CI/CD pipeline with Gitea
- Bundle size enforcement
- HEIC image conversion
- Artist slug-based URLs
In Progress
- Email notifications for appointments
- SMS reminders for clients
- Advanced calendar conflict resolution
- Payment integration (Stripe/Square)
- Gift card system enhancements
- Enhanced analytics dashboard
Future Enhancements
- Client self-service portal
- Online deposit payments
- Artist earnings reports
- Inventory management
- Social media auto-posting
- Mobile app (React Native)
- Webhook integrations
- Advanced reporting
See the open issues for a full list of proposed features and known issues.
Contributing
Contributions are welcome! This project follows standard Git workflows and conventional commits.
Development Workflow
-
Fork the project
# Via Gitea UI or git clone git clone https://git.biohazardvfx.com/nicholai/united-tattoo.git -
Create your feature branch
git checkout -b feat/amazing-feature -
Make your changes
- Follow existing code style (enforced by ESLint/Prettier)
- Add tests for new features
- Update documentation as needed
-
Run quality checks
npm run lint # Check linting npm run format # Format code npm run ci:typecheck # Check types npm run test:run # Run tests npm run ci:budgets # Check bundle sizes -
Commit your changes
git add . git commit -m "feat: add amazing feature"Use Conventional Commits format:
feat:New featurefix:Bug fixdocs:Documentation changesstyle:Formatting, missing semicolons, etc.refactor:Code refactoringtest:Adding testschore:Maintenance tasks
-
Push to your branch
git push origin feat/amazing-feature -
Open a Pull Request
- Via Gitea UI
- Provide clear description of changes
- Reference any related issues
Code Style Guidelines
- TypeScript: Prefer strict typing, avoid
any - React: Use functional components with hooks
- File organization: Keep components modular
- Comments: Explain "why", not "what"
- Tests: Test user-facing behavior, not implementation
Top Contributors
License
This project currently does not have a LICENSE file in the repository. If you intend to use GNU GPLv3 (as referenced in the original README template), please add a
LICENSE or COPYING file with the full license text.Alternatively, consider:
- MIT License - Permissive, allows commercial use
- Apache 2.0 - Permissive with patent grant
- GNU GPLv3 - Copyleft, requires source disclosure
- Proprietary - All rights reserved
Choose A License can help you decide.
Current Status: No license specified. Please add a LICENSE file to clarify usage terms.
Contact
Nicholai Vogel
Project Repository https://git.biohazardvfx.com/nicholai/united-tattoo
Live Website https://united-tattoos.com
Star this repository if you find it helpful!
Made with love for United Tattoo Studio, Fountain, CO
