A modern wishlist application built with Svelte + Vite + Tailwind CSS.
Minimalist design, dark/light theme support, multi-language interface (Russian, English, Serbian), and anonymous gift reservations.
- β Add gifts with category, priority, description, price, and link
- π€ AI-powered autofill: Paste a link or describe a gift β AI extracts name, price, category, priority, and image
- π·οΈ Categories: Electronics, Smart Home, Accessories, Education, and more
- β Priorities: Really want, Would be nice, Just a dream
- π Anonymous reservation with secret codes from gift givers
- β Statuses: Available β Reserved β Purchased
- πΌοΈ Image support (auto-extracted from product URLs via Open Graph metadata)
- ποΈ Delete gifts
- π± Fully responsive design
- πΎ SQLite storage (all data in
wishlist.db) - π¨ Svelte + Tailwind CSS 4 β modern reactive UI
- π Theme toggle (light/dark) with preference persistence
- π Toast notifications for user feedback
- π Multi-language support: Russian, English, Serbian
- π JWT authentication with optional Authentik SSO
- βοΈ Invite-link registration for adding new wishlist owners
- π‘οΈ Admin approval controls for invite creation, admin grants, and AI access
- Node.js (v20 or higher)
# 1. Install root dependencies
npm install
# 2. Install frontend dependencies
cd frontend && npm install && cd ..
# 3. Configure environment variables
cp .env.example .env
# Edit .env and add your JWT_SECRET, USERS, and optional GEMINI_API_KEY
# 4. Run in development mode (backend + frontend)
npm run devThe app will be available at:
- Frontend: http://localhost:5173
- Backend API: http://localhost:3000
Important: To use AI-powered gift autofill, get a free Gemini API key at https://makersuite.google.com/app/apikey and add it to your .env file:
GEMINI_API_KEY=your_api_key_hereFor authenticated wishlist management, configure at least one bootstrap user:
JWT_SECRET=replace_with_a_long_random_secret
USERS=alexey:Alexey:change_me:π:alexey@example.comThe first user in USERS is bootstrapped as an admin and receives AI access. Existing seeded users keep their password if it was changed in the app.
# Build frontend
npm run build
# Start server (will serve static files from frontend/dist/)
npm startThe app supports the current authenticated flow and the older simple deployment style.
Recommended authenticated flow:
- Configure
JWT_SECRETand at least oneUSERSentry. - Log in as the bootstrap admin.
- Open Settings β Admin.
- Create an invite link for a new user.
- The new user opens
#/invite/<token>, chooses a slug/name/password, and registers. - Admin grants
can_use_aiseparately when the user should be allowed to use AI parsing or translation.
Email is optional. Invite links may include an email hint, but registration does not require one.
Legacy fallback:
ADMIN_PASSWORD is still available for older deployments and migration compatibility:
ADMIN_PASSWORD="your_password" npm startAccess rules when authentication is enabled:
- Creating, editing, deleting, or archiving gifts requires the wishlist owner to be logged in.
- AI gift parsing and translation require both login and
can_use_ai=true. - Invite and user access management require
is_admin=true. - Reservation, unreserve, and purchased flows remain available to gift givers through secret codes.
Important production setting: Always set a non-default JWT_SECRET in production.
The app supports three languages:
- π·πΊ Russian (default)
- π¬π§ English
- π·πΈ Serbian
Language preference is saved in localStorage and persists across sessions. The app automatically detects browser language on first visit.
- Svelte 5 β reactive framework with excellent performance
- Vite 7 β lightning-fast build tool
- Tailwind CSS 4 β utility-first CSS with dark theme
- Vitest β unit testing
- Express.js β REST API
- sql.js β SQLite in pure JavaScript (no native compilation)
- Google Generative AI (Gemini) β AI-powered gift parsing from natural language or URLs
- cheerio β Open Graph metadata extraction from product pages
- node-fetch β HTTP client for metadata extraction
- dotenv β environment variables
- express-rate-limit β rate limiting protection
wishlist/
βββ server.js # Express + sql.js backend
βββ server/ # Modular backend structure
β βββ config/
β β βββ env.js # Environment configuration (CORS, etc.)
β βββ middleware/
β β βββ access.js # Admin and AI access checks
β β βββ auth.js # JWT auth helpers
β β βββ rateLimiter.js # Rate limiting
β β βββ validation.js # Request validation
β βββ models/
β β βββ Gift.js # Gift model
β β βββ Invite.js # Invite-token lifecycle
β β βββ User.js # User profiles and access flags
β βββ migrations/
β βββ migrationManager.js # Migration system
β βββ 0001-initial-schema.js
β βββ 0002-add-category-codes.js
β βββ 0003-add-priority-codes.js
β βββ 0004-cleanup-schema.js
βββ package.json # Root dependencies and scripts
βββ .env # Environment variables
βββ wishlist.db # SQLite database
βββ Dockerfile # Docker configuration
βββ docker-compose.yml # Docker Compose configuration
βββ frontend/ # Svelte application
β βββ src/
β β βββ App.svelte # Main component
β β βββ app.css # Global styles with Tailwind
β β βββ lib/
β β βββ GiftCard.svelte # Gift card component
β β βββ InviteRegistration.svelte # Invite-link registration
β β βββ LoginModal.svelte # Login modal
β β βββ AddGiftModal.svelte # Add gift modal with AI autofill
β β βββ EditGiftModal.svelte # Edit gift modal
β β βββ ReserveModal.svelte # Reserve modal
β β βββ DeleteModal.svelte # Delete modal
β β βββ components/ # Reusable components
β β β βββ Toast.svelte
β β β βββ ToastContainer.svelte
β β β βββ LanguageSwitcher.svelte
β β βββ stores/ # Svelte stores
β β β βββ theme.js # Theme management
β β β βββ locale.js # Locale management
β β β βββ toasts.js # Toast notifications
β β βββ locales/ # Translations
β β β βββ ru.json # Russian
β β β βββ en.json # English
β β β βββ sr.json # Serbian
β β β βββ index.js
β β βββ utils/ # Utilities
β β βββ api.js # API client
β β βββ validation.js # Form validation
β β βββ i18n.js # i18n utilities
β βββ tests/ # Unit tests (Vitest)
β βββ package.json
β βββ vite.config.js # Vite config + proxy
β βββ tailwind.config.js # Tailwind config
β βββ postcss.config.js # PostCSS config
β βββ vitest.config.js # Vitest config
β βββ svelte.config.js
βββ CLAUDE.md # Documentation for Claude Code
βββ README.md
- π Theme toggle (light/dark) with automatic system preference detection
- β‘ Minimalist interface without unnecessary effects
- π« Smooth transitions and hover effects
- π± Responsive design for all devices
- π¨ Centralized design system with tokens for colors, typography, spacing
- βΏ Accessible: Proper ARIA labels, keyboard navigation, semantic HTML
Color Palette:
- Primary brand colors with light/dark variants
- Semantic colors: available (emerald), reserved (amber), purchased (emerald)
- Priority colors: hot (red), medium (amber), low (blue)
- Neutral grays for text, borders, backgrounds
Typography Scale:
- Sizes: xs (12px) β 4xl (36px)
- Weights: normal, medium, semibold, bold
- Tracking: normal, tight, tighter, widest
- Predefined combinations for common elements (labels, buttons, modals)
Tokens:
- Spacing: input, modal, xs, sm, md, lg, xl
- Shadows: editorial, editorial-lg, raised
- Radius: modal (12px), button (4px), card (12px)
- Gift giver reserves a gift and enters a secret code
- This code is saved in the database but not displayed in the UI
- Wishlist owner promises not to look at the database before the holiday
- Gift giver can cancel or change status only knowing the code
- After the holiday, the owner can see who gave what (if they want)
The database is created automatically on first run through the migration system.
Initialization includes:
- Creating the
giftstable with all necessary fields - Adding category and priority codes for better i18n support
- Ready to work with categories and priorities
- A bootstrap admin logs in.
- The admin creates an invite link in Settings β Admin.
- The invite link is sent to the new user.
- The new user registers from the invite link. Email is optional.
- The user can create and manage only their own wishlist after login.
- The admin separately enables AI access when needed.
Invite tokens expire automatically and can be revoked before use. Used or revoked invite links cannot register another user.
Most owner/admin write endpoints use JWT authentication:
Authorization: Bearer <token>Log in with slug and password.
{
"slug": "alexey",
"password": "change_me"
}Return the current authenticated user or null.
Create an invite link. Requires admin access.
{
"email": "optional@example.com",
"name_hint": "Optional Name",
"can_use_ai": false
}Return public invite details before registration.
Register a new user from an active invite.
{
"slug": "new-user",
"name": "New User",
"password": "strong-password"
}Update user access flags. Requires admin access.
{
"is_admin": false,
"can_use_ai": true
}Get public wishlist owners.
Get all gifts for a wishlist owner (sorted by priority).
Get information about a specific gift.
Add a new gift. Requires authenticated owner access.
{
"name": "Gift Name",
"description": "Description",
"category_code": "electronics",
"priority_code": "hot",
"link": "https://...",
"image_url": "https://...",
"price": "1000 β½ + Π΄ΠΎΡΡΠ°Π²ΠΊΠ°"
}Category codes: electronics, home, accessories, education, games, clothing, sports, creativity
Priority codes: hot, medium, low
Reserve a gift
{
"secret_code": "SantaHelper123",
"reserved_by": "Gift Giver"
}Cancel reservation
{
"secret_code": "SantaHelper123"
}Mark as purchased
{
"secret_code": "SantaHelper123"
}Archive a received gift. Requires authenticated owner access.
Edit a gift. Requires authenticated owner access.
{
"name": "New Name",
"description": "New Description",
"category_code": "electronics",
"priority_code": "medium",
"link": "https://...",
"image_url": "https://...",
"price": "$100 + shipping"
}Delete a gift. Requires authenticated owner access.
Parse gift information from text or URL using AI. Requires authenticated user with can_use_ai=true.
{
"text": "iPhone 15 Pro 256GB"
}Response:
{
"name": "iPhone 15 Pro 256GB",
"description": null,
"price": null,
"category": "electronics",
"priority": "hot",
"link": null,
"image_url": null
}Supported inputs:
- Natural language: "Π₯ΠΎΡΡ iPhone 15 Pro", "I really want a PS5"
- Product URLs: Automatically extracts name, price, image via Open Graph metadata
- Category detection: Auto-detects from keywords (laptop β electronics, book β education)
- Priority detection: Analyzes phrases like "very want" β hot, "would be nice" β medium
Extract Open Graph metadata from a URL
{
"url": "https://example.com/product"
}Response:
{
"title": "Product Name",
"description": "Product description",
"image": "https://example.com/image.jpg",
"url": "https://example.com/product"
}Allows localhost origins only (ports 5173, 5174, 5175, 5176, 3000)
By default, allows all origins (*) since frontend and backend are served from the same origin.
To restrict access:
Via .env file:
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.comVia Docker:
docker run -d \
-e ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com \
-p 3000:3000 \
wishlist-appVia docker-compose:
environment:
- ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com# With docker-compose (uses image from Docker Hub)
docker-compose up -d
# Or manually
docker run -d \
--name wishlist \
-p 3000:3000 \
-v $(pwd)/wishlist.db:/app/wishlist.db \
-e ADMIN_PASSWORD="your_password" \
-e JWT_SECRET="replace_with_a_long_random_secret" \
-e USERS="alexey:Alexey:change_me:π:alexey@example.com" \
-e ALLOWED_ORIGINS="https://yourdomain.com" \
dzarlax/wishlist-app:latest# Local build with docker-compose
docker-compose -f docker-compose.dev.yml up -d
# Or build manually
docker build -t wishlist-app .
# Run local image
docker run -d \
--name wishlist \
-p 3000:3000 \
-v $(pwd)/wishlist.db:/app/wishlist.db \
-e ADMIN_PASSWORD="your_password" \
-e JWT_SECRET="replace_with_a_long_random_secret" \
-e USERS="alexey:Alexey:change_me:π:alexey@example.com" \
wishlist-app- Invite Registration: Admin-created links are the normal way to add new wishlist owners
- AI Autofill: Paste any product link or describe a gift in plain language β AI will extract all details automatically when your user has AI access
- Image Extraction: When pasting product URLs, the app automatically extracts images via Open Graph metadata
- Persistent Authentication: JWT session is saved in localStorage
- Database: Created automatically on first run, all data saved in
wishlist.db - Gift Sorting: By priority (π₯ > β > π), then by creation date
- Price Format: Stored as text β supports custom formats like "15000 β½ + Π΄ΠΎΡΡΠ°Π²ΠΊΠ°", "$100 + shipping", etc.
- Language Preference: Auto-detected from browser, saved in localStorage
- Keyboard Shortcuts: Press ESC to close any modal, Enter to submit forms
- Accessibility: All modals support keyboard navigation and screen readers
MIT