Skip to content

Disane87/tap-and-tell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

211 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Nuxt Vue TypeScript Tailwind CSS pnpm License GitHub issues

🎯 Tap & Tell β€” NFC-Enabled Digital Guestbook

Hey there! πŸ‘‹ Tap & Tell is a modern, NFC-powered digital guestbook that transforms how guests leave their mark at events. Guests tap their phone on an NFC tag (or scan a QR code), and a beautiful multi-step wizard guides them through leaving their name, photo, and a personal message. No app install required! πŸ“±βœ¨

Perfect for weddings πŸ’, birthday parties πŸŽ‚, corporate events 🏒, or any gathering where you want to capture memories digitally!

Note

πŸ€– AI-Aided Development (AIAD)

This project openly uses AI-assisted development (e.g. Claude Code) to accelerate workflows, improve code quality, and gain more development momentum. All AI-generated code is reviewed and approved by humans β€” this is not a vibe-coding project, but a deliberate effort to build a useful product while exploring the boundaries, benefits, and trade-offs of AI-aided development.


✨ What Can This Thing Do?

Glad you asked! Here's the good stuff:

  • πŸ“± NFC & QR Code Entry β€” Guests tap an NFC tag or scan a QR code to open the guestbook instantly β€” no app download needed!
  • πŸ§™ Multi-Step Wizard β€” A beautiful 4-step form guides guests through leaving their entry (Basics β†’ Favorites β†’ Fun Facts β†’ Message)
  • πŸ“Έ Photo Upload with Compression β€” Guests snap a selfie or upload a photo, automatically compressed client-side for fast uploads
  • 🎨 Polaroid-Style Cards β€” Entries are displayed as gorgeous polaroid-style cards with handwritten fonts
  • πŸŒ™ Dark Mode β€” Full light/dark/system theme support with zero flash of unstyled content (FOUC)
  • 🌍 Multilingual β€” English and German out of the box with @nuxtjs/i18n
  • πŸ–₯️ Slideshow Mode β€” Full-screen auto-advancing slideshow, perfect for displaying on a TV at your event
  • πŸ“„ PDF Export β€” Download your entire guestbook as a beautifully formatted PDF
  • πŸ” Admin Dashboard β€” Password-protected admin panel for entry moderation (approve, reject, delete)
  • πŸ“Š Entry Moderation β€” Three-state system: pending β†’ approved / rejected β€” keep your guestbook clean!
  • πŸ”„ Offline Support β€” Entries are queued in IndexedDB when offline and synced when back online
  • πŸ“± PWA Ready β€” Install as a Progressive Web App on any device
  • 🐳 Docker Support β€” Ready-to-use Dockerfile and docker-compose for easy self-hosting

πŸ“± How It Works

The magic is simple β€” here's the flow:

1. πŸ“² Guest taps NFC tag or scans QR code
         ↓
2. 🌐 Browser opens Tap & Tell (no app install!)
         ↓
3. πŸ§™ 4-step wizard collects:
      Step 1: Name + Photo (required)
      Step 2: Favorites β€” color, food, movie, song, video (optional)
      Step 3: Fun Facts β€” superpowers, hidden talents, preferences (optional)
      Step 4: Personal Message (required)
         ↓
4. πŸ’Ύ Entry saved with photo compression
         ↓
5. πŸŽ‰ Entry appears in the guestbook!

Note

πŸ“ Steps 1 (Basics) and 4 (Message) are required. Steps 2 (Favorites) and 3 (Fun Facts) are completely optional β€” guests can skip them!


πŸš€ Getting Started

Ready to set up your own digital guestbook? Let's go! πŸŽ‰

Prerequisites

  • Node.js 18+ installed
  • pnpm package manager (npm install -g pnpm)

Quick Start

# 1. Clone the repo
git clone https://github.com/Disane87/tap-and-tell.git
cd tap-and-tell

# 2. Install dependencies
pnpm install

# 3. Start the dev server
pnpm dev

That's it! Open http://localhost:3000 and you're running! πŸŽ‰

Environment Variables

Create a .env file in the project root:

# PostgreSQL connection string
POSTGRES_URL=postgresql://user:password@localhost:5432/tapandtell

# JWT signing secret (CHANGE THIS in production!)
JWT_SECRET=your-jwt-secret-here

# CSRF token signing secret (CHANGE THIS in production!)
CSRF_SECRET=your-csrf-secret-here

# Master encryption key for photo encryption (64 hex chars, REQUIRED in production!)
ENCRYPTION_MASTER_KEY=

# Storage directory for entries and photos
DATA_DIR=.data

Caution

⚠️ Security First! Always set secure values for JWT_SECRET, CSRF_SECRET, and ENCRYPTION_MASTER_KEY in production!


🐳 Docker Deployment

Prefer containers? We've got you covered!

Docker Compose β€” Production (Recommended)

The docker-compose.prod.yml is a self-contained stack (app + PostgreSQL) ready for Portainer or any Docker host.

1. Generate secrets

All secrets must be set before first start. Generate them with openssl:

# JWT signing secret (64-char hex)
openssl rand -hex 32

# CSRF token secret (64-char hex)
openssl rand -hex 32

# Photo encryption master key (64-char hex)
openssl rand -hex 32

# PostgreSQL password (64-char hex)
openssl rand -hex 32

# API token secret (base64)
openssl rand -base64 32

2. Configure environment variables

Set the generated values in docker-compose.prod.yml or pass them as environment variables:

Variable Format Description
POSTGRES_PASSWORD 64-char hex PostgreSQL password (same in postgres and app services)
JWT_SECRET 64-char hex JWT signing key for authentication
CSRF_SECRET 64-char hex CSRF double-submit cookie secret
ENCRYPTION_MASTER_KEY 64-char hex AES-256-GCM photo encryption key
TOKEN_SECRET base64 string API token signing secret
DB_SSL "false" Set to "false" for Docker-to-Docker connections (no SSL). Omit for external DBs (Neon, Supabase) where SSL is required.

Caution

Never commit secrets to version control. Use environment variables, Docker secrets, or Portainer's environment variable UI instead.

3. Deploy

# Direct Docker Compose
docker compose -f docker-compose.prod.yml up -d

Or in Portainer: Stacks β†’ Add Stack β†’ paste the compose file β†’ set environment variables in the UI.

Docker Compose β€” Development

docker compose up -d

Standalone Docker

# Build the image
docker build -t tap-and-tell .

# Run the container
docker run -d \
  -p 3000:3000 \
  -e POSTGRES_URL=postgresql://user:password@host:5432/tapandtell \
  -e DB_SSL=false \
  -e JWT_SECRET=$(openssl rand -hex 32) \
  -e CSRF_SECRET=$(openssl rand -hex 32) \
  -e ENCRYPTION_MASTER_KEY=$(openssl rand -hex 32) \
  -e TOKEN_SECRET=$(openssl rand -base64 32) \
  -v tap-and-tell-data:/app/data \
  tap-and-tell

Important

πŸ“‚ Mount a volume to /app/data to persist your guestbook entries and photos across container restarts!


πŸ“– Pages & Features

Here's a tour of everything Tap & Tell offers:

🏠 Landing Page (/)

The main entry point for guests! Features:

  • 🎠 Swipeable Carousel β€” Intro slide followed by existing entry slides
  • πŸ“ Bottom Sheet Wizard β€” The 4-step form slides up from the bottom
  • ⌨️ Keyboard & Swipe Navigation β€” Navigate entries with arrow keys or swipe gestures
  • πŸ“± NFC Context Detection β€” Personalized welcome when entering via NFC tag
  • πŸ”΅ Pagination Dots β€” Visual indicators for carousel position

πŸ“– Guestbook (/guestbook)

Browse all approved entries in a beautiful grid:

  • πŸ” Search by Name β€” Debounced search (300ms) for instant filtering
  • πŸ”„ Sort Options β€” Newest first or oldest first
  • πŸ“„ PDF Export β€” Download the entire guestbook as a formatted PDF
  • πŸ–₯️ Slideshow Link β€” Quick access to slideshow mode
  • πŸ‘† Detail View β€” Click any card to see the full entry in a bottom sheet

πŸ–₯️ Slideshow (/slideshow)

Perfect for displaying on a TV at your event!

  • ▢️ Auto-Advancing β€” Configurable interval (3–30 seconds, default 8)
  • ⏸️ Play/Pause Controls β€” Take control when you want
  • πŸ–₯️ Fullscreen Mode β€” True fullscreen for maximum impact
  • ⌨️ Keyboard Controls β€” Arrow keys, Space, P (pause), F (fullscreen), ESC (exit)
  • πŸ‘» Auto-Hide Controls β€” Controls fade away during playback

πŸ” Admin Dashboard (/admin)

Manage your guestbook with a password-protected admin panel:

  • πŸ“Š Status Tabs β€” Filter by All, Pending, Approved, Rejected
  • βœ… Bulk Actions β€” Approve or reject multiple entries at once
  • πŸ—‘οΈ Individual Management β€” Delete or change status of single entries
  • πŸ”’ Entry Counts β€” See counts per status at a glance
  • πŸšͺ Secure Logout β€” Token-based session management

πŸ“± QR Code Generator (/admin/qr)

Generate QR codes for your event:

  • 🎯 Custom Event Name β€” Embed your event name in the URL
  • πŸ“₯ Download Options β€” Export as PNG or SVG
  • πŸ“‹ Copy URL β€” Quick copy to clipboard
  • πŸ”— NFC-Compatible URLs β€” Generates ?source=nfc&event=YourEvent links

πŸ—οΈ Architecture

Let's peek under the hood! Here's how Tap & Tell is built:

Tech Stack

Layer Technology
Framework Nuxt 4.3 (SSR disabled β€” client-side SPA)
UI Library Vue 3.5 with Composition API
Styling Tailwind CSS v4 via @tailwindcss/vite
Components shadcn-vue (headless UI)
Language TypeScript 5.9
Icons Lucide Vue Next
Database PostgreSQL 16+ with Row-Level Security (RLS)
ORM Drizzle ORM
Auth JWT (jose) + 2FA (TOTP / Email OTP)
Encryption AES-256-GCM per-tenant photo encryption
i18n @nuxtjs/i18n (EN + DE)
PDF Generation jsPDF
QR Codes qrcode
Utilities VueUse
Toasts vue-sonner
PWA @vite-pwa/nuxt
Package Manager pnpm
Deployment Docker (self-hosted)

Project Structure

tap-and-tell/
β”œβ”€β”€ app/                          # πŸ–₯️ Nuxt client application
β”‚   β”œβ”€β”€ pages/                    #    Route pages
β”‚   β”œβ”€β”€ components/               #    Vue components
β”‚   β”‚   β”œβ”€β”€ form/                 #    Wizard form steps
β”‚   β”‚   └── ui/                   #    shadcn-vue base components
β”‚   β”œβ”€β”€ composables/              #    Vue composables (state & logic)
β”‚   β”œβ”€β”€ types/                    #    TypeScript type definitions
β”‚   β”œβ”€β”€ plugins/                  #    Nuxt client plugins
β”‚   β”œβ”€β”€ layouts/                  #    Page layouts
β”‚   β”œβ”€β”€ lib/                      #    Utility functions
β”‚   └── assets/                   #    Static assets (CSS, images)
β”‚
β”œβ”€β”€ server/                       # βš™οΈ Nitro server
β”‚   β”œβ”€β”€ routes/api/               #    API endpoints
β”‚   β”‚   β”œβ”€β”€ g/                    #    Public guest endpoints (flat routes)
β”‚   β”‚   β”œβ”€β”€ auth/                 #    Authentication + 2FA
β”‚   β”‚   β”œβ”€β”€ tenants/              #    Tenant/guestbook management
β”‚   β”‚   └── photos/               #    Photo serving (encrypted)
β”‚   β”œβ”€β”€ database/                 #    Schema + migrations (Drizzle ORM)
β”‚   β”œβ”€β”€ utils/                    #    Server utilities (crypto, auth, storage)
β”‚   └── plugins/                  #    Server startup plugins
β”‚
β”œβ”€β”€ i18n/                         # 🌍 Internationalization
β”‚   └── locales/                  #    EN + DE translation files
β”‚
β”œβ”€β”€ public/                       # πŸ“‚ Static public assets
β”‚   └── icons/                    #    PWA icons
β”‚
β”œβ”€β”€ plans/                        # πŸ“‹ Development plan documents
β”œβ”€β”€ nuxt.config.ts                # βš™οΈ Nuxt configuration
β”œβ”€β”€ package.json                  # πŸ“¦ Dependencies
└── tsconfig.json                 # πŸ”§ TypeScript config

Storage Layer

Tap & Tell uses PostgreSQL 16+ with Row-Level Security (RLS) for multi-tenant data isolation. Photos are stored on disk with AES-256-GCM per-tenant encryption.

PostgreSQL (via Drizzle ORM)
β”œβ”€β”€ users, sessions, user_two_factor     # Auth & 2FA
β”œβ”€β”€ tenants, tenant_members              # Multi-tenancy
β”œβ”€β”€ guestbooks, entries                  # Core data (RLS-protected)
└── audit_logs, api_apps, api_tokens     # Security & API access

.data/photos/
β”œβ”€β”€ [guestbookId]/[entryId].[ext]        # AES-256-GCM encrypted photos
└── ...

Note

Photo storage is configurable via STORAGE_DRIVER (local, vercel-blob, or s3) and DATA_DIR (default: .data/).


πŸ”Œ API Reference

All API endpoints at a glance. Authenticated endpoints use HTTP-only JWT cookies with CSRF protection.

Public β€” Guest Endpoints (No Auth)

Method Endpoint Description
GET /api/g/[id]/info Guestbook info (name, settings, type)
GET /api/g/[id]/entries Approved entries for a guestbook
POST /api/g/[id]/entries Create a new guest entry (rate-limited)
GET /api/photos/[tenantId]/[filename] Serve encrypted photo
GET /api/health Health check endpoint
GET /api/og Locale-aware OG image (?lang=de|en)

Authentication & Profile

Method Endpoint Description
POST /api/auth/login Login with email/password, set JWT cookies
POST /api/auth/register Register a new account
POST /api/auth/logout Clear auth cookies
POST /api/auth/refresh Refresh access token
GET /api/auth/me Get current user profile
PUT /api/auth/me Update name and/or email
DELETE /api/auth/me Delete account (requires password)
PUT /api/auth/password Change password
GET /api/auth/csrf Get CSRF token
POST /api/auth/avatar Upload avatar (multipart, max 5 MB)
DELETE /api/auth/avatar Delete avatar
GET /api/auth/avatar/[userId] Serve avatar image (public)

Two-Factor Authentication (2FA)

Method Endpoint Description
POST /api/auth/2fa/setup Start 2FA setup (returns QR code)
POST /api/auth/2fa/verify-setup Verify TOTP code to activate 2FA
GET /api/auth/2fa/status Check if 2FA is enabled
POST /api/auth/2fa/verify Verify 2FA code during login
POST /api/auth/2fa/disable Disable 2FA
POST /api/auth/2fa/resend Resend email OTP code

Tenant Management (JWT Required)

Method Endpoint Description
GET /api/tenants List user's tenants
POST /api/tenants Create a new tenant
GET /api/tenants/[uuid] Get tenant details
PUT /api/tenants/[uuid] Update tenant settings
DELETE /api/tenants/[uuid] Delete tenant
POST /api/tenants/[uuid]/rotate-key Rotate encryption key

Guestbook Management (JWT Required)

Method Endpoint Description
GET /api/tenants/[uuid]/guestbooks List guestbooks with entry counts
POST /api/tenants/[uuid]/guestbooks Create a new guestbook
GET /api/tenants/[uuid]/guestbooks/[gbUuid] Get guestbook details
PUT /api/tenants/[uuid]/guestbooks/[gbUuid] Update guestbook settings
DELETE /api/tenants/[uuid]/guestbooks/[gbUuid] Delete guestbook (cascades entries)
POST /api/tenants/[uuid]/guestbooks/[gbUuid]/header Upload header image
DELETE /api/tenants/[uuid]/guestbooks/[gbUuid]/header Delete header image
POST /api/tenants/[uuid]/guestbooks/[gbUuid]/background Upload background image
DELETE /api/tenants/[uuid]/guestbooks/[gbUuid]/background Delete background image

Entry Moderation (JWT Required)

Method Endpoint Description
GET /api/tenants/[uuid]/guestbooks/[gbUuid]/entries All entries (admin view)
PATCH /api/tenants/[uuid]/guestbooks/[gbUuid]/entries/[id] Update entry status
DELETE /api/tenants/[uuid]/guestbooks/[gbUuid]/entries/[id] Delete an entry
POST /api/tenants/[uuid]/guestbooks/[gbUuid]/entries/bulk Bulk status update

Team Members (JWT Required)

Method Endpoint Description
GET /api/tenants/[uuid]/members List team members
POST /api/tenants/[uuid]/members/invite Invite team member
GET /api/tenants/[uuid]/members/invites List pending invites
DELETE /api/tenants/[uuid]/members/invites/[id] Cancel invite
DELETE /api/tenants/[uuid]/members/[userId] Remove team member
GET /api/invites/[token] Get invite details (public)
POST /api/invites/accept Accept team invite (public)

API Apps & Tokens (JWT Required)

Method Endpoint Description
GET /api/tenants/[uuid]/apps List API apps
POST /api/tenants/[uuid]/apps Create API app
GET /api/tenants/[uuid]/apps/[appId] Get API app details
PUT /api/tenants/[uuid]/apps/[appId] Update API app
DELETE /api/tenants/[uuid]/apps/[appId] Delete API app
GET /api/tenants/[uuid]/apps/[appId]/tokens List tokens
POST /api/tenants/[uuid]/apps/[appId]/tokens Create token
DELETE /api/tenants/[uuid]/apps/[appId]/tokens/[tokenId] Revoke token
GET /api/scopes List available API scopes

Analytics (JWT Required)

Method Endpoint Description
POST /api/analytics/events Track analytics event
GET /api/tenants/[uuid]/analytics/overview Dashboard overview
GET /api/tenants/[uuid]/analytics/traffic Traffic analytics
GET /api/tenants/[uuid]/analytics/sources Traffic sources
GET /api/tenants/[uuid]/analytics/devices Device breakdown
GET /api/tenants/[uuid]/analytics/funnel Conversion funnel

Quick Start Example

# Login
curl -X POST /api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password"}'
# β†’ Sets access_token and refresh_token cookies

# Create a guest entry (public, no auth)
curl -X POST /api/g/your-guestbook-id/entries \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "message": "What an amazing event!",
    "photo": "data:image/jpeg;base64,..."
  }'

🧩 Composables

The brain of Tap & Tell lives in these composables β€” each one handles a specific concern:

Composable What It Does
useAuth() πŸ” JWT cookie-based authentication (login, register, logout, profile)
useGuests() πŸ“‹ CRUD operations for guestbook entries. Module-level shared state across the app
useGuestForm() πŸ§™ 4-step wizard state management with per-step validation
useGuestbook() πŸ“– Public guestbook operations using flat /api/g/[id] endpoints
useTenantAdmin() πŸ‘” Admin entry operations (fetch all, delete, update status, bulk)
useTheme() πŸŒ™ Light/dark/system theme with localStorage persistence & FOUC prevention
useNfc() πŸ“± Detects NFC context from URL query params (?source=nfc&event=...)
useSlideshow() πŸ–₯️ Auto-advancing slideshow with play/pause/fullscreen controls
useEntryFilters() πŸ” Debounced search & sort for the guestbook page
usePdfExport() πŸ“„ Multi-page PDF generation with photos, favorites, and fun facts
useImageCompression() πŸ“Έ Client-side image compression (max 1920px, target 500KB)
useOfflineQueue() πŸ”„ IndexedDB-based offline entry queuing with auto-sync

🎨 Theme System

Tap & Tell features a 3-layer theme initialization to prevent any flash of unstyled content (FOUC):

Layer 1: Inline <script> in <head>
  └─ Runs BEFORE first paint
  └─ Reads localStorage, applies `dark` class to <html>
  └─ Zero visual flash! ⚑

Layer 2: Client Plugin (theme.client.ts)
  └─ Syncs reactive Vue state with DOM
  └─ Listens for system preference changes

Layer 3: <ClientOnly> Wrapper
  └─ ThemeToggle component only renders on client
  └─ Prevents SSR hydration mismatches

Toggle between Light β˜€οΈ, Dark πŸŒ™, and System πŸ’» modes with a single click.


🌍 Internationalization

All user-facing text is translatable β€” no hardcoded strings anywhere!

Feature Details
Languages πŸ‡¬πŸ‡§ English (default) + πŸ‡©πŸ‡ͺ Deutsch
Strategy No URL prefix, browser detection
Persistence Cookie i18n_locale
Module @nuxtjs/i18n

Translation files live in i18n/locales/ covering all scopes: form, guestbook, admin, navigation, slideshow, toasts, and more.


πŸ“± NFC & QR Code Setup

Setting up NFC tags or QR codes for your event is easy!

NFC Tags

  1. Get writable NFC tags (NTAG215 or similar)
  2. Use any NFC writer app to write the URL:
    https://your-domain.com/?source=nfc&event=YourEventName
    
  3. Place tags at your event venue β€” guests tap and they're in! πŸ“²

QR Codes

  1. Go to /admin/qr in your admin panel
  2. Enter your event name
  3. Download as PNG or SVG
  4. Print and display at your venue! πŸ–¨οΈ

Tip

πŸ’‘ Pro tip: Use both NFC tags AND QR codes! NFC for quick access, QR as a fallback for phones without NFC support.


πŸ“„ Data Model

Here's what a guest entry looks like under the hood:

interface GuestEntry {
  id: string                    // UUID
  name: string                  // Guest's name
  message: string               // Personal message
  photoUrl?: string             // Photo path (e.g., /api/photos/{id}.jpg)
  answers?: GuestAnswers        // Optional form answers
  createdAt: string             // ISO 8601 timestamp
  status?: EntryStatus          // 'pending' | 'approved' | 'rejected'
  rejectionReason?: string      // Why entry was rejected
}

interface GuestAnswers {
  // 🎨 Favorites
  favoriteColor?: string
  favoriteFood?: string
  favoriteMovie?: string
  favoriteSong?: { title: string; artist?: string; url?: string }
  favoriteVideo?: { title: string; url?: string }

  // 🎭 Fun Facts
  superpower?: string
  hiddenTalent?: string
  desertIslandItems?: string
  coffeeOrTea?: 'coffee' | 'tea'
  nightOwlOrEarlyBird?: 'night_owl' | 'early_bird'
  beachOrMountains?: 'beach' | 'mountains'

  // πŸ“– Our Story
  howWeMet?: string
  bestMemory?: string
}

⚑ Advanced Configuration

Image Compression Settings

Client-side image compression is applied automatically before upload:

Setting Value
Max dimension 1920px
Target file size 500KB
Initial JPEG quality 0.8
Minimum JPEG quality 0.3 (adaptive)

PWA Configuration

Tap & Tell is a fully-configured Progressive Web App:

Setting Value
Display mode Standalone
Orientation Portrait
Theme color Dark
Icon SVG (any size, maskable)
Font caching Google Fonts (1-year CacheFirst)
Offline Navigate fallback to /

Server-Side Validation

Entries are validated server-side with these constraints:

Field Constraint
name Required, 1–100 characters
message Required, 1–1000 characters
photo Optional, max 7MB (base64)

Auth Token Details

Property Value
Algorithm HS256 (JWT via jose)
Access Token 15 minutes, HTTP-only cookie
Refresh Token 7 days, HTTP-only cookie, stored in DB
CSRF Double-submit cookie pattern
2FA TOTP (RFC 6238) + Email OTP

πŸ› οΈ Development

Want to contribute or customize? Here's how to get the development environment running:

Commands

pnpm install              # Install dependencies
pnpm dev                  # Start development server (https://localhost:3000)
pnpm build                # Build for production
pnpm preview              # Preview production build locally
pnpm exec nuxi typecheck  # Run TypeScript type checking

Key Architectural Decisions

Here are the "why"s behind the design:

Decision Reasoning
SSR Disabled Client-side SPA avoids hydration mismatches with localStorage, NFC APIs, and browser-only features
Module-Level State Composables use module-level ref() instead of useState() to prevent SSR payload conflicts
PostgreSQL + RLS Multi-tenant isolation via Row-Level Security, per-tenant encryption for photos
JWT Cookies HTTP-only access (15min) + refresh (7d) tokens with CSRF protection
Client-Side Compression Reduces upload size and server load β€” images compressed before sending
IndexedDB Offline Queue Entries are never lost, even without internet β€” syncs automatically when back online
3-Layer Theme Init Prevents FOUC completely β€” no flash between page load and theme application

🀝 Contributing

Want to make Tap & Tell even better? That's awesome! πŸŽ‰

Here's how to get started:

  1. 🍴 Fork the repository
  2. 🌿 Create a feature branch (git checkout -b feature/amazing-feature)
  3. πŸ’» Make your changes
  4. βœ… Build to verify (pnpm build)
  5. πŸ“ Commit with conventional commits (feat: add amazing feature)
  6. πŸš€ Push and open a Pull Request

Guidelines

  • πŸ”€ Code comments & JSDoc in English
  • 🌍 All user-facing text must use i18n translation keys
  • 🎨 Styling with Tailwind CSS utility classes
  • 🧩 UI components follow shadcn-vue conventions
  • β™Ώ Accessibility (a11y) best practices
  • πŸ“ TypeScript β€” avoid any type
  • πŸ”’ Security β€” no hardcoded secrets, validate at boundaries

Commit Convention

We use Conventional Commits:

feat: add new feature
fix: resolve a bug
docs: update documentation
refactor: restructure code
style: formatting changes
test: add or update tests
chore: maintenance tasks

πŸ”’ Security Notes

Caution

Before going to production, make sure to:

  • πŸ”‘ Set a secure JWT_SECRET (not the default)
  • πŸ”‘ Set a secure CSRF_SECRET (not the default)
  • πŸ”‘ Generate a 64-character hex ENCRYPTION_MASTER_KEY for photo encryption
  • πŸ” All admin features require 2FA (TOTP or Email OTP)

πŸ’‘ Use Case Ideas

Here are some creative ways to use Tap & Tell:

  • πŸ’ Weddings β€” Let guests leave their wishes and photos for the couple
  • πŸŽ‚ Birthday Parties β€” Collect fun facts and memories from attendees
  • 🏒 Corporate Events β€” Gather feedback and networking connections
  • πŸŽ“ Graduations β€” Classmates share their favorite memories
  • πŸŽ„ Holiday Parties β€” Guests share their holiday traditions and wishes
  • 🏠 Housewarming β€” Visitors leave advice and well-wishes for the new home
  • 🎸 Concerts & Festivals β€” Fans share their experience and favorite moments

πŸŽ‰ That's a Wrap!

Thanks for checking out Tap & Tell! If you find it useful, give it a ⭐ on GitHub β€” it really helps! πŸ™Œ

Got a bug to report? Have an idea for a new feature? Open an issue and let's make this better together! πŸš€


Made with ❀️ using Nuxt, Vue, and Tailwind CSS

About

Tap & Tell is a modern, NFC-powered digital guestbook that transforms how guests leave their mark at events. Guests tap their phone on an NFC tag (or scan a QR code), and a beautiful multi-step wizard guides them through leaving their name, photo, and a personal message. No app install required! πŸ“±βœ¨

Topics

Resources

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors