initial working version
This commit is contained in:
51
wishlist-app/.dockerignore
Normal file
51
wishlist-app/.dockerignore
Normal file
@@ -0,0 +1,51 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
|
||||
# Build outputs
|
||||
build
|
||||
.svelte-kit
|
||||
dist
|
||||
.output
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Development files
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
SETUP.md
|
||||
*.md
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# Misc
|
||||
.prettierrc
|
||||
.eslintrc*
|
||||
.editorconfig
|
||||
3
wishlist-app/.env.example
Normal file
3
wishlist-app/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
# Database connection string
|
||||
# Example: postgresql://username:password@localhost:5432/wishlist
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/wishlist
|
||||
23
wishlist-app/.gitignore
vendored
Normal file
23
wishlist-app/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
1
wishlist-app/.npmrc
Normal file
1
wishlist-app/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
engine-strict=true
|
||||
272
wishlist-app/COOLIFY_DEPLOYMENT.md
Normal file
272
wishlist-app/COOLIFY_DEPLOYMENT.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Coolify Deployment Guide
|
||||
|
||||
This guide will help you deploy the Wishlist app to Coolify using Docker.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Coolify instance running (self-hosted or cloud)
|
||||
- PostgreSQL database (can be created in Coolify)
|
||||
- Git repository (GitHub, GitLab, or Gitea)
|
||||
|
||||
## Step 1: Push Code to Git Repository
|
||||
|
||||
If you haven't already, push your code to a Git repository:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
git remote add origin <your-repo-url>
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## Step 2: Set Up PostgreSQL in Coolify
|
||||
|
||||
1. Go to your Coolify dashboard
|
||||
2. Click **+ New Resource** → **Database** → **PostgreSQL**
|
||||
3. Configure:
|
||||
- **Name**: `wishlist-db`
|
||||
- **PostgreSQL Version**: 16 (or latest)
|
||||
- Click **Create**
|
||||
4. Once created, note down the connection details:
|
||||
- **Host**: (internal hostname, e.g., `wishlist-db`)
|
||||
- **Port**: `5432`
|
||||
- **Database**: `postgres` (or create a new database)
|
||||
- **Username**: `postgres`
|
||||
- **Password**: (auto-generated or set your own)
|
||||
|
||||
## Step 3: Create the Application in Coolify
|
||||
|
||||
1. Click **+ New Resource** → **Application**
|
||||
2. Select your Git source (GitHub, GitLab, etc.)
|
||||
3. Choose your repository
|
||||
4. Configure the application:
|
||||
- **Branch**: `main` (or your default branch)
|
||||
- **Build Pack**: Docker
|
||||
- **Port**: `3000`
|
||||
- **Dockerfile Location**: `./Dockerfile` (default)
|
||||
|
||||
## Step 4: Configure Environment Variables
|
||||
|
||||
In the Coolify application settings, go to **Environment Variables** and add:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@wishlist-db:5432/postgres
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
**Important**: Replace `YOUR_PASSWORD` with the actual password from Step 2.
|
||||
|
||||
### Getting the Database Connection String
|
||||
|
||||
If you created the database in Coolify, you can find the connection string in:
|
||||
1. Go to your database resource
|
||||
2. Click on **Connection String** or **Environment Variables**
|
||||
3. Copy the `DATABASE_URL` or construct it as:
|
||||
```
|
||||
postgresql://[USERNAME]:[PASSWORD]@[HOST]:[PORT]/[DATABASE]
|
||||
```
|
||||
|
||||
## Step 5: Configure Health Check (Optional but Recommended)
|
||||
|
||||
In Coolify application settings:
|
||||
1. Go to **Health Check**
|
||||
2. Set:
|
||||
- **Path**: `/`
|
||||
- **Port**: `3000`
|
||||
- **Interval**: `30s`
|
||||
|
||||
## Step 6: Deploy
|
||||
|
||||
1. Click **Deploy** button in Coolify
|
||||
2. Monitor the build logs
|
||||
3. Wait for the deployment to complete
|
||||
|
||||
The build process will:
|
||||
- Install dependencies with Bun
|
||||
- Build the SvelteKit application
|
||||
- Create a production-ready Docker image
|
||||
- Start the application on port 3000
|
||||
|
||||
## Step 7: Run Database Migrations
|
||||
|
||||
After the first deployment, you need to set up the database schema. You have two options:
|
||||
|
||||
### Option A: Using Coolify Terminal (Recommended)
|
||||
|
||||
1. Go to your application in Coolify
|
||||
2. Click **Terminal** or **Console**
|
||||
3. Run:
|
||||
```bash
|
||||
bun run db:push
|
||||
```
|
||||
|
||||
### Option B: Using Docker Exec
|
||||
|
||||
SSH into your Coolify server and run:
|
||||
```bash
|
||||
docker exec -it <container-name> bun run db:push
|
||||
```
|
||||
|
||||
Find the container name with:
|
||||
```bash
|
||||
docker ps | grep wishlist
|
||||
```
|
||||
|
||||
## Step 8: Access Your Application
|
||||
|
||||
1. In Coolify, go to your application
|
||||
2. You should see the generated domain (e.g., `wishlist-xyz.coolify.io`)
|
||||
3. Optionally, configure a custom domain in **Domains** settings
|
||||
|
||||
Visit your domain and your wishlist app should be running! 🎉
|
||||
|
||||
## Updating the Application
|
||||
|
||||
For future updates:
|
||||
|
||||
1. Push changes to your Git repository:
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Your changes"
|
||||
git push
|
||||
```
|
||||
|
||||
2. In Coolify, click **Deploy** to rebuild and redeploy
|
||||
|
||||
Or enable **Auto Deploy** in Coolify settings to deploy automatically on push.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails
|
||||
|
||||
**Check the build logs in Coolify for specific errors.**
|
||||
|
||||
Common issues:
|
||||
- Missing environment variables
|
||||
- Wrong Node/Bun version
|
||||
- Database connection issues during build
|
||||
|
||||
### Application Crashes
|
||||
|
||||
1. Check application logs in Coolify
|
||||
2. Verify `DATABASE_URL` is correct
|
||||
3. Ensure database is running and accessible
|
||||
4. Check if migrations were run
|
||||
|
||||
### Database Connection Errors
|
||||
|
||||
```
|
||||
Error: Connection refused
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
- Verify the database hostname (use internal Coolify network name)
|
||||
- Check database is running: Go to database resource in Coolify
|
||||
- Ensure application and database are in the same network/project
|
||||
- Verify credentials are correct
|
||||
|
||||
### Port Issues
|
||||
|
||||
```
|
||||
Error: Port 3000 already in use
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
- Coolify handles port mapping automatically
|
||||
- Don't change the PORT environment variable unless needed
|
||||
- Check if another service is using port 3000
|
||||
|
||||
### Migration Errors
|
||||
|
||||
```
|
||||
Error: relation "wishlists" does not exist
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
Run the database migration:
|
||||
```bash
|
||||
docker exec -it <container-name> bun run db:push
|
||||
```
|
||||
|
||||
## Environment-Specific Configuration
|
||||
|
||||
### Using Multiple Databases
|
||||
|
||||
For different environments (staging/production):
|
||||
|
||||
1. Create separate database resources in Coolify
|
||||
2. Use different `DATABASE_URL` for each environment
|
||||
3. Deploy to different branches or applications
|
||||
|
||||
### SSL/TLS for Database
|
||||
|
||||
If using an external PostgreSQL with SSL:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom Domain
|
||||
|
||||
1. In Coolify, go to application → **Domains**
|
||||
2. Click **Add Domain**
|
||||
3. Enter your domain (e.g., `wishlist.yourdomain.com`)
|
||||
4. Configure DNS:
|
||||
- Add A record pointing to your Coolify server IP
|
||||
- Or CNAME pointing to your Coolify domain
|
||||
5. Coolify will automatically configure SSL with Let's Encrypt
|
||||
|
||||
### Scaling
|
||||
|
||||
To scale your application:
|
||||
|
||||
1. In Coolify, adjust resources:
|
||||
- **CPU Limit**
|
||||
- **Memory Limit**
|
||||
2. Consider using a managed PostgreSQL service for better performance
|
||||
3. Enable multiple replicas (if your Coolify setup supports it)
|
||||
|
||||
### Backup Database
|
||||
|
||||
In Coolify database settings:
|
||||
1. Go to **Backups**
|
||||
2. Configure automatic backups
|
||||
3. Set backup frequency and retention
|
||||
|
||||
Or manually backup:
|
||||
```bash
|
||||
docker exec -it <db-container> pg_dump -U postgres postgres > backup.sql
|
||||
```
|
||||
|
||||
## Production Checklist
|
||||
|
||||
Before going live:
|
||||
|
||||
- [ ] Database backups configured
|
||||
- [ ] Custom domain configured with SSL
|
||||
- [ ] Environment variables set correctly
|
||||
- [ ] Database migrations run successfully
|
||||
- [ ] Health checks configured
|
||||
- [ ] Application logs monitored
|
||||
- [ ] Test wishlist creation and reservation flow
|
||||
- [ ] Test on mobile devices
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Check Coolify documentation: https://coolify.io/docs
|
||||
2. Review application logs in Coolify dashboard
|
||||
3. Verify all environment variables are set
|
||||
4. Ensure database is accessible from the application
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Set up monitoring (Coolify has built-in monitoring)
|
||||
- Configure alerts for downtime
|
||||
- Set up automated backups
|
||||
- Consider CDN for static assets (if needed)
|
||||
387
wishlist-app/DOCKER.md
Normal file
387
wishlist-app/DOCKER.md
Normal file
@@ -0,0 +1,387 @@
|
||||
# Docker Deployment Guide
|
||||
|
||||
This application is fully containerized and ready for Docker deployment.
|
||||
|
||||
## Quick Start with Docker Compose
|
||||
|
||||
The easiest way to run the entire stack locally:
|
||||
|
||||
```bash
|
||||
# Start the application and database
|
||||
docker-compose up -d
|
||||
|
||||
# Check if containers are running
|
||||
docker-compose ps
|
||||
|
||||
# Run database migrations
|
||||
docker-compose exec app bun run db:push
|
||||
|
||||
# View application logs
|
||||
docker-compose logs -f app
|
||||
|
||||
# View database logs
|
||||
docker-compose logs -f db
|
||||
```
|
||||
|
||||
Visit `http://localhost:3000`
|
||||
|
||||
### Stopping
|
||||
|
||||
```bash
|
||||
# Stop containers
|
||||
docker-compose down
|
||||
|
||||
# Stop and remove volumes (⚠️ deletes data)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
## Building the Docker Image
|
||||
|
||||
### Local Build
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -t wishlist-app .
|
||||
|
||||
# Run the container (requires database)
|
||||
docker run -d \
|
||||
-p 3000:3000 \
|
||||
-e DATABASE_URL="postgresql://user:pass@host:5432/db" \
|
||||
--name wishlist-app \
|
||||
wishlist-app
|
||||
```
|
||||
|
||||
### Multi-stage Build Details
|
||||
|
||||
The Dockerfile uses a multi-stage build for optimization:
|
||||
|
||||
1. **base**: Base Bun image
|
||||
2. **deps**: Install dependencies with frozen lockfile
|
||||
3. **builder**: Build the SvelteKit application
|
||||
4. **runner**: Production image with minimal size
|
||||
|
||||
Final image includes:
|
||||
- Built application (`/app/build`)
|
||||
- Production dependencies
|
||||
- Drizzle schema for migrations
|
||||
- Port 3000 exposed
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Required environment variables:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://username:password@host:port/database
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
### For docker-compose
|
||||
|
||||
Edit `docker-compose.yml` to change database credentials.
|
||||
|
||||
### For Coolify
|
||||
|
||||
Set in the Coolify dashboard under **Environment Variables**.
|
||||
|
||||
## Database Migrations
|
||||
|
||||
### Initial Setup
|
||||
|
||||
After first deployment:
|
||||
|
||||
```bash
|
||||
# Using docker-compose
|
||||
docker-compose exec app bun run db:push
|
||||
|
||||
# Using standalone container
|
||||
docker exec -it wishlist-app bun run db:push
|
||||
```
|
||||
|
||||
### Applying Schema Changes
|
||||
|
||||
After modifying `src/lib/server/schema.ts`:
|
||||
|
||||
```bash
|
||||
# Generate migration
|
||||
bun run db:generate
|
||||
|
||||
# Apply to running container
|
||||
docker-compose exec app bun run db:push
|
||||
```
|
||||
|
||||
## Port Configuration
|
||||
|
||||
Default port: `3000`
|
||||
|
||||
To change:
|
||||
|
||||
1. **docker-compose.yml**:
|
||||
```yaml
|
||||
ports:
|
||||
- "8080:3000" # External:Internal
|
||||
```
|
||||
|
||||
2. **Dockerfile** (if needed):
|
||||
```dockerfile
|
||||
ENV PORT=3000
|
||||
EXPOSE 3000
|
||||
```
|
||||
|
||||
## Volumes and Data Persistence
|
||||
|
||||
### PostgreSQL Data
|
||||
|
||||
Data is persisted in a Docker volume:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
To backup:
|
||||
```bash
|
||||
docker-compose exec db pg_dump -U wishlistuser wishlist > backup.sql
|
||||
```
|
||||
|
||||
To restore:
|
||||
```bash
|
||||
docker-compose exec -T db psql -U wishlistuser wishlist < backup.sql
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
### Application Health
|
||||
|
||||
The app responds on `http://localhost:3000/`
|
||||
|
||||
### Database Health
|
||||
|
||||
PostgreSQL health check is configured in docker-compose.yml:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U wishlistuser -d wishlist"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
```
|
||||
|
||||
## Production Considerations
|
||||
|
||||
### Security
|
||||
|
||||
1. **Change default credentials** in docker-compose.yml
|
||||
2. **Use secrets** for sensitive data:
|
||||
```yaml
|
||||
secrets:
|
||||
db_password:
|
||||
file: ./secrets/db_password.txt
|
||||
```
|
||||
|
||||
3. **Don't expose PostgreSQL port** in production:
|
||||
```yaml
|
||||
# Remove or comment out:
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Resource Limits**:
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
```
|
||||
|
||||
2. **Use PostgreSQL connection pooling** for high traffic
|
||||
|
||||
### Networking
|
||||
|
||||
For production with reverse proxy (Nginx, Traefik):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
networks:
|
||||
- traefik_network
|
||||
- internal
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.wishlist.rule=Host(`wishlist.example.com`)"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container won't start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
docker-compose logs app
|
||||
|
||||
# Common issues:
|
||||
# - DATABASE_URL incorrect
|
||||
# - Database not ready
|
||||
# - Port already in use
|
||||
```
|
||||
|
||||
### Database connection failed
|
||||
|
||||
```bash
|
||||
# Test database connectivity
|
||||
docker-compose exec app sh
|
||||
bun run db:push
|
||||
|
||||
# Check database is running
|
||||
docker-compose ps db
|
||||
|
||||
# Restart database
|
||||
docker-compose restart db
|
||||
```
|
||||
|
||||
### Build fails
|
||||
|
||||
```bash
|
||||
# Clear build cache
|
||||
docker-compose build --no-cache
|
||||
|
||||
# Check Docker resources
|
||||
docker system df
|
||||
docker system prune # Clean up if needed
|
||||
```
|
||||
|
||||
### Permission errors
|
||||
|
||||
```bash
|
||||
# Fix file permissions
|
||||
sudo chown -R $USER:$USER .
|
||||
|
||||
# Rebuild
|
||||
docker-compose up --build -d
|
||||
```
|
||||
|
||||
## Development with Docker
|
||||
|
||||
### Live Development
|
||||
|
||||
For development with hot reload, mount source:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
command: bun run dev
|
||||
volumes:
|
||||
- ./src:/app/src
|
||||
- ./static:/app/static
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
```
|
||||
|
||||
### Access Database
|
||||
|
||||
```bash
|
||||
# Using psql
|
||||
docker-compose exec db psql -U wishlistuser wishlist
|
||||
|
||||
# Using Drizzle Studio
|
||||
docker-compose exec app bun run db:studio
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build image
|
||||
run: docker build -t wishlist-app .
|
||||
- name: Push to registry
|
||||
run: |
|
||||
docker tag wishlist-app registry.example.com/wishlist-app
|
||||
docker push registry.example.com/wishlist-app
|
||||
```
|
||||
|
||||
## Coolify Deployment
|
||||
|
||||
For Coolify deployment, see [COOLIFY_DEPLOYMENT.md](./COOLIFY_DEPLOYMENT.md)
|
||||
|
||||
Coolify will:
|
||||
1. Pull from your Git repository
|
||||
2. Build using this Dockerfile
|
||||
3. Deploy with configured environment variables
|
||||
4. Set up networking and SSL automatically
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Container Stats
|
||||
|
||||
```bash
|
||||
# Real-time stats
|
||||
docker stats wishlist-app wishlist-db
|
||||
|
||||
# Resource usage
|
||||
docker-compose top
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# Follow logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Last 100 lines
|
||||
docker-compose logs --tail=100
|
||||
|
||||
# Specific service
|
||||
docker-compose logs -f app
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
```bash
|
||||
# Stop and remove containers
|
||||
docker-compose down
|
||||
|
||||
# Remove images
|
||||
docker rmi wishlist-app
|
||||
|
||||
# Remove all unused data
|
||||
docker system prune -a
|
||||
|
||||
# Remove specific volume
|
||||
docker volume rm wishlist-app_postgres_data
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. ✅ Use `.dockerignore` to reduce build context
|
||||
2. ✅ Multi-stage builds for smaller images
|
||||
3. ✅ Non-root user in production
|
||||
4. ✅ Health checks configured
|
||||
5. ✅ Secrets management for credentials
|
||||
6. ✅ Resource limits defined
|
||||
7. ✅ Regular backups of database
|
||||
8. ✅ Monitoring and logging
|
||||
9. ✅ Security scanning of images
|
||||
10. ✅ Version tags for images
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Docker Documentation](https://docs.docker.com/)
|
||||
- [Docker Compose Documentation](https://docs.docker.com/compose/)
|
||||
- [Coolify Documentation](https://coolify.io/docs)
|
||||
- [SvelteKit Deployment](https://kit.svelte.dev/docs/adapters)
|
||||
39
wishlist-app/Dockerfile
Normal file
39
wishlist-app/Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
# Use Bun's official image
|
||||
FROM oven/bun:1 AS base
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
FROM base AS deps
|
||||
COPY package.json bun.lockb ./
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
# Build the application
|
||||
FROM base AS builder
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Build the SvelteKit app
|
||||
RUN bun run build
|
||||
|
||||
# Production image
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder /app/build ./build
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
|
||||
# Copy Drizzle files for migrations
|
||||
COPY --from=builder /app/drizzle ./drizzle
|
||||
COPY --from=builder /app/drizzle.config.ts ./
|
||||
COPY --from=builder /app/src/lib/server/schema.ts ./src/lib/server/schema.ts
|
||||
|
||||
# Expose the port
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the application
|
||||
CMD ["bun", "run", "./build/index.js"]
|
||||
227
wishlist-app/README.md
Normal file
227
wishlist-app/README.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Wishlist App
|
||||
|
||||
A simple, self-contained wishlist application built with SvelteKit, Tailwind CSS, Drizzle ORM, and PostgreSQL. Create wishlists and share them with friends and family via secure links.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎁 Create wishlists with items (title, description, links, images, prices, priorities)
|
||||
- 🔗 Two types of links:
|
||||
- **Owner Link**: Edit and manage your wishlist
|
||||
- **Public Link**: Share with friends to view and reserve items
|
||||
- 🔒 Link-based security (no accounts required)
|
||||
- 👥 Reserve items with optional name
|
||||
- 📱 Fully responsive mobile design
|
||||
- 🎨 Clean, modern UI with shadcn components
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework**: SvelteKit 2 with Svelte 5
|
||||
- **Styling**: Tailwind CSS v4
|
||||
- **Database**: PostgreSQL with Drizzle ORM
|
||||
- **UI Components**: shadcn-svelte
|
||||
- **Runtime**: Bun
|
||||
- **TypeScript**: Full type safety
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 🐳 Docker (Recommended for Quick Testing)
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
docker-compose exec app bun run db:push
|
||||
```
|
||||
|
||||
Visit `http://localhost:3000` 🎉
|
||||
|
||||
See [DOCKER.md](./DOCKER.md) for complete Docker documentation.
|
||||
|
||||
### 📚 Deployment Guides
|
||||
|
||||
- **[COOLIFY_DEPLOYMENT.md](./COOLIFY_DEPLOYMENT.md)** - Deploy to Coolify (recommended for production)
|
||||
- **[DOCKER.md](./DOCKER.md)** - Docker & docker-compose guide
|
||||
- **[SETUP.md](./SETUP.md)** - Local development setup
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Bun](https://bun.sh/) installed (for local development)
|
||||
- PostgreSQL database (local, Docker, or hosted)
|
||||
- Docker (optional, for containerized deployment)
|
||||
|
||||
## Getting Started (Local Development)
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
### 2. Set Up Environment Variables
|
||||
|
||||
Create a `.env` file in the root directory:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` and add your PostgreSQL connection string:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://username:password@localhost:5432/wishlist
|
||||
```
|
||||
|
||||
### 3. Set Up the Database
|
||||
|
||||
Push the database schema (easiest for development):
|
||||
|
||||
```bash
|
||||
bun run db:push
|
||||
```
|
||||
|
||||
Or use migrations (recommended for production):
|
||||
|
||||
```bash
|
||||
bun run db:generate
|
||||
bun run db:migrate
|
||||
```
|
||||
|
||||
### 4. Start the Development Server
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
The app will be available at `http://localhost:5173`
|
||||
|
||||
## Database Commands
|
||||
|
||||
- `bun run db:generate` - Generate new migration from schema changes
|
||||
- `bun run db:migrate` - Run migrations
|
||||
- `bun run db:push` - Push schema directly to database (development)
|
||||
- `bun run db:studio` - Open Drizzle Studio to browse your database
|
||||
|
||||
## How It Works
|
||||
|
||||
### Creating a Wishlist
|
||||
|
||||
1. Visit the home page
|
||||
2. Enter a title and optional description
|
||||
3. Click "Create Wishlist"
|
||||
4. You'll be redirected to the owner edit page with your unique owner link
|
||||
|
||||
### Managing Your Wishlist (Owner)
|
||||
|
||||
- Add items with details (name, description, URL, image, price, priority)
|
||||
- Delete items
|
||||
- See which items have been reserved (but not who reserved them for surprise protection)
|
||||
- Share the public link with friends and family
|
||||
- Keep your owner link private to maintain edit access
|
||||
|
||||
### Viewing and Reserving Items (Public)
|
||||
|
||||
- Open the public link shared by the wishlist owner
|
||||
- Browse available items
|
||||
- Click "Reserve This" on any item to claim it
|
||||
- Optionally add your name so others can coordinate
|
||||
- Cancel your reservation if plans change
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── lib/
|
||||
│ ├── components/ui/ # shadcn-svelte components
|
||||
│ ├── server/
|
||||
│ │ ├── db.ts # Database connection
|
||||
│ │ └── schema.ts # Drizzle schema definitions
|
||||
│ └── utils.ts # Utility functions
|
||||
├── routes/
|
||||
│ ├── api/wishlists/ # API endpoints
|
||||
│ ├── wishlist/[token]/ # Public view page
|
||||
│ │ └── edit/ # Owner edit page
|
||||
│ └── +page.svelte # Home page
|
||||
└── app.css # Global styles with Tailwind
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### wishlists
|
||||
- `id` - UUID primary key
|
||||
- `title` - Wishlist title
|
||||
- `description` - Optional description
|
||||
- `ownerToken` - Unique token for editing
|
||||
- `publicToken` - Unique token for viewing
|
||||
- `createdAt`, `updatedAt` - Timestamps
|
||||
|
||||
### items
|
||||
- `id` - UUID primary key
|
||||
- `wishlistId` - Foreign key to wishlists
|
||||
- `title` - Item name
|
||||
- `description` - Optional description
|
||||
- `link` - Optional product URL
|
||||
- `imageUrl` - Optional image URL
|
||||
- `price` - Optional price
|
||||
- `priority` - high | medium | low
|
||||
- `isReserved` - Boolean flag
|
||||
- `createdAt`, `updatedAt` - Timestamps
|
||||
|
||||
### reservations
|
||||
- `id` - UUID primary key
|
||||
- `itemId` - Foreign key to items
|
||||
- `reserverName` - Optional name of person who reserved
|
||||
- `createdAt` - Timestamp
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Links use cryptographically secure random IDs (cuid2)
|
||||
- Owner and public tokens are separate and unique
|
||||
- No authentication system means links should be treated as passwords
|
||||
- Owner links should be kept private
|
||||
- Public links can be shared freely
|
||||
|
||||
## Deployment
|
||||
|
||||
### Coolify (Docker) - Recommended
|
||||
|
||||
This application includes a Dockerfile optimized for Coolify deployment.
|
||||
|
||||
📖 **See [COOLIFY_DEPLOYMENT.md](./COOLIFY_DEPLOYMENT.md) for complete deployment guide**
|
||||
|
||||
Quick steps:
|
||||
1. Push code to Git repository
|
||||
2. Create PostgreSQL database in Coolify
|
||||
3. Create new application in Coolify
|
||||
4. Set `DATABASE_URL` environment variable
|
||||
5. Deploy and run `bun run db:push` to set up schema
|
||||
|
||||
### Build for Production
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
```
|
||||
|
||||
### Preview Production Build
|
||||
|
||||
```bash
|
||||
bun run preview
|
||||
```
|
||||
|
||||
### Other Platforms
|
||||
|
||||
This app uses `@sveltejs/adapter-node` for Docker/Node.js deployments, but can be adapted for:
|
||||
|
||||
- **Vercel/Netlify**: Change to `@sveltejs/adapter-auto`
|
||||
- **Cloudflare Pages**: Use `@sveltejs/adapter-cloudflare`
|
||||
- **Static**: Use `@sveltejs/adapter-static` (requires API route adjustments)
|
||||
|
||||
Make sure to set your `DATABASE_URL` environment variable in your deployment platform.
|
||||
|
||||
## Development
|
||||
|
||||
- All components are fully typed with TypeScript
|
||||
- UI components follow shadcn-svelte patterns
|
||||
- Mobile-first responsive design using Tailwind
|
||||
- Server-side rendering for better performance and SEO
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
149
wishlist-app/SETUP.md
Normal file
149
wishlist-app/SETUP.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Quick Setup Guide
|
||||
|
||||
Follow these steps to get your wishlist app running:
|
||||
|
||||
## Choose Your Setup Method
|
||||
|
||||
### Method 1: Docker Compose (Easiest) 🐳
|
||||
|
||||
If you have Docker installed, this is the fastest way to get started:
|
||||
|
||||
```bash
|
||||
# Start everything with Docker
|
||||
docker-compose up -d
|
||||
|
||||
# Run database migrations
|
||||
docker-compose exec app bun run db:push
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f app
|
||||
```
|
||||
|
||||
Visit `http://localhost:3000` 🎉
|
||||
|
||||
To stop:
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Method 2: Local Development (Traditional)
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
Make sure you have:
|
||||
- ✅ Bun installed (already done!)
|
||||
- ✅ PostgreSQL database running
|
||||
|
||||
### Setting up PostgreSQL (if needed)
|
||||
|
||||
#### Option A: Local PostgreSQL
|
||||
```bash
|
||||
# Install PostgreSQL (Ubuntu/Debian)
|
||||
sudo apt install postgresql postgresql-contrib
|
||||
|
||||
# Start PostgreSQL
|
||||
sudo systemctl start postgresql
|
||||
|
||||
# Create a database
|
||||
sudo -u postgres createdb wishlist
|
||||
|
||||
# Create a user and grant permissions
|
||||
sudo -u postgres psql
|
||||
CREATE USER wishlistuser WITH PASSWORD 'yourpassword';
|
||||
GRANT ALL PRIVILEGES ON DATABASE wishlist TO wishlistuser;
|
||||
\q
|
||||
```
|
||||
|
||||
#### Option B: Docker PostgreSQL
|
||||
```bash
|
||||
docker run --name wishlist-postgres \
|
||||
-e POSTGRES_DB=wishlist \
|
||||
-e POSTGRES_USER=wishlistuser \
|
||||
-e POSTGRES_PASSWORD=yourpassword \
|
||||
-p 5432:5432 \
|
||||
-d postgres:16
|
||||
```
|
||||
|
||||
#### Option C: Hosted Database
|
||||
- [Supabase](https://supabase.com/) - Free tier available
|
||||
- [Neon](https://neon.tech/) - Serverless PostgreSQL
|
||||
- [Railway](https://railway.app/) - Easy deployment
|
||||
|
||||
## 2. Configure Environment
|
||||
|
||||
Create `.env` file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` with your database connection:
|
||||
```env
|
||||
DATABASE_URL=postgresql://wishlistuser:yourpassword@localhost:5432/wishlist
|
||||
```
|
||||
|
||||
## 3. Setup Database
|
||||
|
||||
```bash
|
||||
# Push the schema to your database
|
||||
bun run db:push
|
||||
```
|
||||
|
||||
## 4. Start Development Server
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Visit `http://localhost:5173` 🎉
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
If you get connection errors:
|
||||
|
||||
1. Check PostgreSQL is running:
|
||||
```bash
|
||||
# Linux/Mac
|
||||
sudo systemctl status postgresql
|
||||
|
||||
# Docker
|
||||
docker ps | grep postgres
|
||||
```
|
||||
|
||||
2. Verify connection string format:
|
||||
```
|
||||
postgresql://username:password@host:port/database
|
||||
```
|
||||
|
||||
3. Test connection:
|
||||
```bash
|
||||
psql "postgresql://wishlistuser:yourpassword@localhost:5432/wishlist"
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
If port 5173 is taken:
|
||||
```bash
|
||||
bun run dev -- --port 3000
|
||||
```
|
||||
|
||||
### Schema Changes
|
||||
|
||||
After modifying `src/lib/server/schema.ts`:
|
||||
```bash
|
||||
bun run db:push
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Create your first wishlist
|
||||
2. Add some items
|
||||
3. Share the public link with friends
|
||||
4. Save your owner link somewhere safe!
|
||||
|
||||
## Production Deployment
|
||||
|
||||
See the main README.md for deployment instructions.
|
||||
455
wishlist-app/bun.lock
Normal file
455
wishlist-app/bun.lock
Normal file
@@ -0,0 +1,455 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "wishlist-app",
|
||||
"dependencies": {
|
||||
"@internationalized/date": "^3.10.0",
|
||||
"@paralleldrive/cuid2": "^3.0.4",
|
||||
"bits-ui": "^2.14.4",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"postgres": "^3.4.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwind-variants": "^3.1.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-node": "^5.4.0",
|
||||
"@sveltejs/kit": "^2.48.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"drizzle-kit": "^0.31.7",
|
||||
"svelte": "^5.43.8",
|
||||
"svelte-check": "^4.3.4",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||
|
||||
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
|
||||
|
||||
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
|
||||
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||
|
||||
"@internationalized/date": ["@internationalized/date@3.10.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
|
||||
|
||||
"@paralleldrive/cuid2": ["@paralleldrive/cuid2@3.0.4", "", { "dependencies": { "@noble/hashes": "^2.0.1", "bignumber.js": "^9.3.1", "error-causes": "^3.0.2" }, "bin": { "cuid2": "bin/cuid2.js" } }, "sha512-sM6M2PWrByOEpN2QYAdulhEbSZmChwj0e52u4hpwB7u4PznFiNAavtE6m7O8tWUlzX+jT2eKKtc5/ZgX+IHrtg=="],
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.9", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA=="],
|
||||
|
||||
"@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="],
|
||||
|
||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||
|
||||
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.7", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q=="],
|
||||
|
||||
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.0", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw=="],
|
||||
|
||||
"@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.4.0", "", { "dependencies": { "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.9.5" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-NMsrwGVPEn+J73zH83Uhss/hYYZN6zT3u31R3IHAn3MiKC3h8fjmIAhLfTSOeNHr5wPYfjjMg8E+1gyFgyrEcQ=="],
|
||||
|
||||
"@sveltejs/kit": ["@sveltejs/kit@2.49.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA=="],
|
||||
|
||||
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="],
|
||||
|
||||
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.17", "", { "dependencies": { "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "tailwindcss": "4.1.17" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA=="],
|
||||
|
||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"bits-ui": ["bits-ui@2.14.4", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
|
||||
|
||||
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"devalue": ["devalue@5.5.0", "", {}, "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.31.7", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-hOzRGSdyKIU4FcTSFYGKdXEjFsncVwHZ43gY3WU5Bz9j5Iadp6Rh6hxLSQ1IWXpKLBKt/d5y1cpSPcV+FcoQ1A=="],
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||
|
||||
"error-causes": ["error-causes@3.0.2", "", {}, "sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||
|
||||
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||
|
||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||
|
||||
"esrap": ["esrap@2.1.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-T/Dhhv/QH+yYmiaLz9SA3PW+YyenlnRKDNdtlYJrSOBmNsH4nvPux+mTwx7p+wAedlJrGoZtXNI0a0MjQ2QkVg=="],
|
||||
|
||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
|
||||
|
||||
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||
|
||||
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
||||
|
||||
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||
|
||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
|
||||
|
||||
"runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="],
|
||||
|
||||
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
||||
|
||||
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"svelte": ["svelte@5.43.14", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-pHeUrp1A5S6RGaXhJB7PtYjL1VVjbVrJ2EfuAoPu9/1LeoMaJa/pcdCsCSb0gS4eUHAHnhCbUDxORZyvGK6kOQ=="],
|
||||
|
||||
"svelte-check": ["svelte-check@4.3.4", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw=="],
|
||||
|
||||
"svelte-toolbelt": ["svelte-toolbelt@0.10.6", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.35.1", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ=="],
|
||||
|
||||
"tabbable": ["tabbable@6.3.0", "", {}, "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
|
||||
|
||||
"tailwind-variants": ["tailwind-variants@3.1.1", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-ftLXe3krnqkMHsuBTEmaVUXYovXtPyTK7ckEfDRXS8PBZx0bAUas+A0jYxuKA5b8qg++wvQ3d2MQ7l/xeZxbZQ=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"vite": ["vite@7.2.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||
|
||||
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||
|
||||
"@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||
}
|
||||
}
|
||||
38
wishlist-app/docker-compose.yml
Normal file
38
wishlist-app/docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: wishlist-db
|
||||
environment:
|
||||
POSTGRES_USER: wishlistuser
|
||||
POSTGRES_PASSWORD: wishlistpassword
|
||||
POSTGRES_DB: wishlist
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U wishlistuser -d wishlist"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: wishlist-app
|
||||
environment:
|
||||
DATABASE_URL: postgresql://wishlistuser:wishlistpassword@db:5432/wishlist
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
10
wishlist-app/drizzle.config.ts
Normal file
10
wishlist-app/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Config } from 'drizzle-kit';
|
||||
|
||||
export default {
|
||||
schema: './src/lib/server/schema.ts',
|
||||
out: './drizzle',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL || ''
|
||||
}
|
||||
} satisfies Config;
|
||||
35
wishlist-app/drizzle/0000_last_steve_rogers.sql
Normal file
35
wishlist-app/drizzle/0000_last_steve_rogers.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
CREATE TABLE "items" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"wishlist_id" uuid NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"description" text,
|
||||
"link" text,
|
||||
"image_url" text,
|
||||
"price" numeric(10, 2),
|
||||
"priority" text DEFAULT 'medium',
|
||||
"is_reserved" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "reservations" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"item_id" uuid NOT NULL,
|
||||
"reserver_name" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "wishlists" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"description" text,
|
||||
"owner_token" text NOT NULL,
|
||||
"public_token" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "wishlists_owner_token_unique" UNIQUE("owner_token"),
|
||||
CONSTRAINT "wishlists_public_token_unique" UNIQUE("public_token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "items" ADD CONSTRAINT "items_wishlist_id_wishlists_id_fk" FOREIGN KEY ("wishlist_id") REFERENCES "public"."wishlists"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "reservations" ADD CONSTRAINT "reservations_item_id_items_id_fk" FOREIGN KEY ("item_id") REFERENCES "public"."items"("id") ON DELETE cascade ON UPDATE no action;
|
||||
240
wishlist-app/drizzle/meta/0000_snapshot.json
Normal file
240
wishlist-app/drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"id": "94fab923-b1d4-447e-b367-5ee7b7894b14",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.items": {
|
||||
"name": "items",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"wishlist_id": {
|
||||
"name": "wishlist_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"link": {
|
||||
"name": "link",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image_url": {
|
||||
"name": "image_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"price": {
|
||||
"name": "price",
|
||||
"type": "numeric(10, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"priority": {
|
||||
"name": "priority",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'medium'"
|
||||
},
|
||||
"is_reserved": {
|
||||
"name": "is_reserved",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"items_wishlist_id_wishlists_id_fk": {
|
||||
"name": "items_wishlist_id_wishlists_id_fk",
|
||||
"tableFrom": "items",
|
||||
"tableTo": "wishlists",
|
||||
"columnsFrom": [
|
||||
"wishlist_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.reservations": {
|
||||
"name": "reservations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"item_id": {
|
||||
"name": "item_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reserver_name": {
|
||||
"name": "reserver_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"reservations_item_id_items_id_fk": {
|
||||
"name": "reservations_item_id_items_id_fk",
|
||||
"tableFrom": "reservations",
|
||||
"tableTo": "items",
|
||||
"columnsFrom": [
|
||||
"item_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.wishlists": {
|
||||
"name": "wishlists",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"owner_token": {
|
||||
"name": "owner_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"public_token": {
|
||||
"name": "public_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"wishlists_owner_token_unique": {
|
||||
"name": "wishlists_owner_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"owner_token"
|
||||
]
|
||||
},
|
||||
"wishlists_public_token_unique": {
|
||||
"name": "wishlists_public_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"public_token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
13
wishlist-app/drizzle/meta/_journal.json
Normal file
13
wishlist-app/drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1763822218153,
|
||||
"tag": "0000_last_steve_rogers",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
41
wishlist-app/package.json
Normal file
41
wishlist-app/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "wishlist-app",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-node": "^5.4.0",
|
||||
"@sveltejs/kit": "^2.48.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"drizzle-kit": "^0.31.7",
|
||||
"svelte": "^5.43.8",
|
||||
"svelte-check": "^4.3.4",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@internationalized/date": "^3.10.0",
|
||||
"@paralleldrive/cuid2": "^3.0.4",
|
||||
"bits-ui": "^2.14.4",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"postgres": "^3.4.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwind-variants": "^3.1.1"
|
||||
}
|
||||
}
|
||||
23
wishlist-app/scripts/docker-migrate.sh
Executable file
23
wishlist-app/scripts/docker-migrate.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to run database migrations in Docker container
|
||||
# Usage: ./scripts/docker-migrate.sh
|
||||
|
||||
echo "🔄 Running database migrations..."
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q wishlist-app; then
|
||||
echo "❌ Error: wishlist-app container is not running"
|
||||
echo " Start it with: docker-compose up -d"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run the migration
|
||||
docker-compose exec app bun run db:push
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Database schema updated successfully!"
|
||||
else
|
||||
echo "❌ Migration failed. Check the error above."
|
||||
exit 1
|
||||
fi
|
||||
34
wishlist-app/src/app.css
Normal file
34
wishlist-app/src/app.css
Normal file
@@ -0,0 +1,34 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-background: hsl(0 0% 100%);
|
||||
--color-foreground: hsl(222.2 84% 4.9%);
|
||||
--color-card: hsl(0 0% 100%);
|
||||
--color-card-foreground: hsl(222.2 84% 4.9%);
|
||||
--color-popover: hsl(0 0% 100%);
|
||||
--color-popover-foreground: hsl(222.2 84% 4.9%);
|
||||
--color-primary: hsl(222.2 47.4% 11.2%);
|
||||
--color-primary-foreground: hsl(210 40% 98%);
|
||||
--color-secondary: hsl(210 40% 96.1%);
|
||||
--color-secondary-foreground: hsl(222.2 47.4% 11.2%);
|
||||
--color-muted: hsl(210 40% 96.1%);
|
||||
--color-muted-foreground: hsl(215.4 16.3% 46.9%);
|
||||
--color-accent: hsl(210 40% 96.1%);
|
||||
--color-accent-foreground: hsl(222.2 47.4% 11.2%);
|
||||
--color-destructive: hsl(0 84.2% 60.2%);
|
||||
--color-destructive-foreground: hsl(210 40% 98%);
|
||||
--color-border: hsl(214.3 31.8% 91.4%);
|
||||
--color-input: hsl(214.3 31.8% 91.4%);
|
||||
--color-ring: hsl(222.2 84% 4.9%);
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
border-color: hsl(var(--color-border));
|
||||
}
|
||||
body {
|
||||
background-color: hsl(var(--color-background));
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
}
|
||||
13
wishlist-app/src/app.d.ts
vendored
Normal file
13
wishlist-app/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
11
wishlist-app/src/app.html
Normal file
11
wishlist-app/src/app.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
1
wishlist-app/src/lib/assets/favicon.svg
Normal file
1
wishlist-app/src/lib/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
83
wishlist-app/src/lib/components/ui/button/button.svelte
Normal file
83
wishlist-app/src/lib/components/ui/button/button.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from '$lib/utils.js';
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*="size-"])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
|
||||
outline:
|
||||
'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline'
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
});
|
||||
|
||||
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
|
||||
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
|
||||
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
||||
WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let {
|
||||
class: className,
|
||||
variant = 'default',
|
||||
size = 'default',
|
||||
ref = $bindable(null),
|
||||
href = undefined,
|
||||
type = 'button',
|
||||
disabled,
|
||||
children,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
href={disabled ? undefined : href}
|
||||
aria-disabled={disabled}
|
||||
role={disabled ? 'link' : undefined}
|
||||
tabindex={disabled ? -1 : undefined}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
16
wishlist-app/src/lib/components/ui/button/index.ts
Normal file
16
wishlist-app/src/lib/components/ui/button/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import Root, {
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
buttonVariants
|
||||
} from './button.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant
|
||||
};
|
||||
14
wishlist-app/src/lib/components/ui/card/card-content.svelte
Normal file
14
wishlist-app/src/lib/components/ui/card/card-content.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLAttributes<HTMLDivElement> & {
|
||||
children?: any;
|
||||
};
|
||||
|
||||
let { class: className, children, ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn('p-6 pt-0', className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLAttributes<HTMLParagraphElement> & {
|
||||
children?: any;
|
||||
};
|
||||
|
||||
let { class: className, children, ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<p class={cn('text-sm text-muted-foreground', className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</p>
|
||||
14
wishlist-app/src/lib/components/ui/card/card-header.svelte
Normal file
14
wishlist-app/src/lib/components/ui/card/card-header.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLAttributes<HTMLDivElement> & {
|
||||
children?: any;
|
||||
};
|
||||
|
||||
let { class: className, children, ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn('flex flex-col space-y-1.5 p-6', className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
14
wishlist-app/src/lib/components/ui/card/card-title.svelte
Normal file
14
wishlist-app/src/lib/components/ui/card/card-title.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||
children?: any;
|
||||
};
|
||||
|
||||
let { class: className, children, ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<h3 class={cn('font-semibold leading-none tracking-tight', className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</h3>
|
||||
17
wishlist-app/src/lib/components/ui/card/card.svelte
Normal file
17
wishlist-app/src/lib/components/ui/card/card.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLAttributes<HTMLDivElement> & {
|
||||
children?: any;
|
||||
};
|
||||
|
||||
let { class: className, children, ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn('rounded-xl border bg-card text-card-foreground shadow', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
19
wishlist-app/src/lib/components/ui/card/index.ts
Normal file
19
wishlist-app/src/lib/components/ui/card/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import Root from './card.svelte';
|
||||
import Content from './card-content.svelte';
|
||||
import Description from './card-description.svelte';
|
||||
import Header from './card-header.svelte';
|
||||
import Title from './card-title.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Header,
|
||||
Title,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle
|
||||
};
|
||||
3
wishlist-app/src/lib/components/ui/input/index.ts
Normal file
3
wishlist-app/src/lib/components/ui/input/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Root from './input.svelte';
|
||||
|
||||
export { Root, Root as Input };
|
||||
20
wishlist-app/src/lib/components/ui/input/input.svelte
Normal file
20
wishlist-app/src/lib/components/ui/input/input.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLInputAttributes & {
|
||||
value?: string | number;
|
||||
};
|
||||
|
||||
let { class: className, type = 'text', value = $bindable(''), ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<input
|
||||
type={type}
|
||||
class={cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
3
wishlist-app/src/lib/components/ui/label/index.ts
Normal file
3
wishlist-app/src/lib/components/ui/label/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Root from './label.svelte';
|
||||
|
||||
export { Root, Root as Label };
|
||||
20
wishlist-app/src/lib/components/ui/label/label.svelte
Normal file
20
wishlist-app/src/lib/components/ui/label/label.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLLabelAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLLabelAttributes & {
|
||||
children?: any;
|
||||
};
|
||||
|
||||
let { class: className, children, ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<label
|
||||
class={cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</label>
|
||||
3
wishlist-app/src/lib/components/ui/textarea/index.ts
Normal file
3
wishlist-app/src/lib/components/ui/textarea/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Root from './textarea.svelte';
|
||||
|
||||
export { Root, Root as Textarea };
|
||||
19
wishlist-app/src/lib/components/ui/textarea/textarea.svelte
Normal file
19
wishlist-app/src/lib/components/ui/textarea/textarea.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
||||
|
||||
type Props = HTMLTextareaAttributes & {
|
||||
value?: string;
|
||||
};
|
||||
|
||||
let { class: className, value = $bindable(''), ...restProps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<textarea
|
||||
class={cn(
|
||||
'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{...restProps}
|
||||
></textarea>
|
||||
1
wishlist-app/src/lib/index.ts
Normal file
1
wishlist-app/src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
7
wishlist-app/src/lib/server/db.ts
Normal file
7
wishlist-app/src/lib/server/db.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import { DATABASE_URL } from '$env/static/private';
|
||||
import * as schema from './schema';
|
||||
|
||||
const client = postgres(DATABASE_URL);
|
||||
export const db = drizzle(client, { schema });
|
||||
64
wishlist-app/src/lib/server/schema.ts
Normal file
64
wishlist-app/src/lib/server/schema.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { pgTable, text, timestamp, numeric, boolean, uuid } from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
|
||||
export const wishlists = pgTable('wishlists', {
|
||||
id: uuid('id').primaryKey().$defaultFn(() => createId()),
|
||||
title: text('title').notNull(),
|
||||
description: text('description'),
|
||||
ownerToken: text('owner_token').notNull().unique(),
|
||||
publicToken: text('public_token').notNull().unique(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const wishlistsRelations = relations(wishlists, ({ many }) => ({
|
||||
items: many(items)
|
||||
}));
|
||||
|
||||
export const items = pgTable('items', {
|
||||
id: uuid('id').primaryKey().$defaultFn(() => createId()),
|
||||
wishlistId: uuid('wishlist_id')
|
||||
.notNull()
|
||||
.references(() => wishlists.id, { onDelete: 'cascade' }),
|
||||
title: text('title').notNull(),
|
||||
description: text('description'),
|
||||
link: text('link'),
|
||||
imageUrl: text('image_url'),
|
||||
price: numeric('price', { precision: 10, scale: 2 }),
|
||||
priority: text('priority').$type<'high' | 'medium' | 'low'>().default('medium'),
|
||||
isReserved: boolean('is_reserved').default(false).notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const itemsRelations = relations(items, ({ one, many }) => ({
|
||||
wishlist: one(wishlists, {
|
||||
fields: [items.wishlistId],
|
||||
references: [wishlists.id]
|
||||
}),
|
||||
reservations: many(reservations)
|
||||
}));
|
||||
|
||||
export const reservations = pgTable('reservations', {
|
||||
id: uuid('id').primaryKey().$defaultFn(() => createId()),
|
||||
itemId: uuid('item_id')
|
||||
.notNull()
|
||||
.references(() => items.id, { onDelete: 'cascade' }),
|
||||
reserverName: text('reserver_name'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const reservationsRelations = relations(reservations, ({ one }) => ({
|
||||
item: one(items, {
|
||||
fields: [reservations.itemId],
|
||||
references: [items.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export type Wishlist = typeof wishlists.$inferSelect;
|
||||
export type NewWishlist = typeof wishlists.$inferInsert;
|
||||
export type Item = typeof items.$inferSelect;
|
||||
export type NewItem = typeof items.$inferInsert;
|
||||
export type Reservation = typeof reservations.$inferSelect;
|
||||
export type NewReservation = typeof reservations.$inferInsert;
|
||||
8
wishlist-app/src/lib/utils.ts
Normal file
8
wishlist-app/src/lib/utils.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export type WithElementRef<T> = T & { ref?: any };
|
||||
12
wishlist-app/src/routes/+layout.svelte
Normal file
12
wishlist-app/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import '../app.css';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
{@render children()}
|
||||
70
wishlist-app/src/routes/+page.svelte
Normal file
70
wishlist-app/src/routes/+page.svelte
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '$lib/components/ui/card';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let title = $state('');
|
||||
let description = $state('');
|
||||
let isCreating = $state(false);
|
||||
|
||||
async function createWishlist() {
|
||||
if (!title.trim()) return;
|
||||
|
||||
isCreating = true;
|
||||
try {
|
||||
const response = await fetch('/api/wishlists', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title, description })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const { ownerToken } = await response.json();
|
||||
goto(`/wishlist/${ownerToken}/edit`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create wishlist:', error);
|
||||
} finally {
|
||||
isCreating = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen flex items-center justify-center p-4 bg-gradient-to-br from-slate-50 to-slate-100">
|
||||
<Card class="w-full max-w-lg">
|
||||
<CardHeader>
|
||||
<CardTitle class="text-3xl">Create Your Wishlist</CardTitle>
|
||||
<CardDescription>
|
||||
Create a wishlist and share it with friends and family
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form on:submit|preventDefault={createWishlist} class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="title">Wishlist Title</Label>
|
||||
<Input
|
||||
id="title"
|
||||
bind:value={title}
|
||||
placeholder="My Birthday Wishlist"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Description (optional)</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
bind:value={description}
|
||||
placeholder="Add some context for your wishlist..."
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" class="w-full" disabled={isCreating || !title.trim()}>
|
||||
{isCreating ? 'Creating...' : 'Create Wishlist'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
28
wishlist-app/src/routes/api/wishlists/+server.ts
Normal file
28
wishlist-app/src/routes/api/wishlists/+server.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { wishlists } from '$lib/server/schema';
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const { title, description } = await request.json();
|
||||
|
||||
if (!title?.trim()) {
|
||||
return json({ error: 'Title is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const ownerToken = createId();
|
||||
const publicToken = createId();
|
||||
|
||||
const [wishlist] = await db
|
||||
.insert(wishlists)
|
||||
.values({
|
||||
title: title.trim(),
|
||||
description: description?.trim() || null,
|
||||
ownerToken,
|
||||
publicToken
|
||||
})
|
||||
.returning();
|
||||
|
||||
return json({ ownerToken, publicToken, id: wishlist.id });
|
||||
};
|
||||
83
wishlist-app/src/routes/wishlist/[token]/+page.server.ts
Normal file
83
wishlist-app/src/routes/wishlist/[token]/+page.server.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { wishlists, items, reservations } from '$lib/server/schema';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const wishlist = await db.query.wishlists.findFirst({
|
||||
where: eq(wishlists.publicToken, params.token),
|
||||
with: {
|
||||
items: {
|
||||
with: {
|
||||
reservations: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!wishlist) {
|
||||
throw error(404, 'Wishlist not found');
|
||||
}
|
||||
|
||||
return {
|
||||
wishlist
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
reserve: async ({ request }) => {
|
||||
const formData = await request.formData();
|
||||
const itemId = formData.get('itemId') as string;
|
||||
const reserverName = formData.get('reserverName') as string;
|
||||
|
||||
if (!itemId) {
|
||||
return { success: false, error: 'Item ID is required' };
|
||||
}
|
||||
|
||||
// Check if already reserved
|
||||
const existingReservation = await db.query.reservations.findFirst({
|
||||
where: eq(reservations.itemId, itemId)
|
||||
});
|
||||
|
||||
if (existingReservation) {
|
||||
return { success: false, error: 'This item is already reserved' };
|
||||
}
|
||||
|
||||
// Create reservation and update item
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.insert(reservations).values({
|
||||
itemId,
|
||||
reserverName: reserverName?.trim() || null
|
||||
});
|
||||
|
||||
await tx
|
||||
.update(items)
|
||||
.set({ isReserved: true })
|
||||
.where(eq(items.id, itemId));
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
unreserve: async ({ request }) => {
|
||||
const formData = await request.formData();
|
||||
const itemId = formData.get('itemId') as string;
|
||||
|
||||
if (!itemId) {
|
||||
return { success: false, error: 'Item ID is required' };
|
||||
}
|
||||
|
||||
// Delete reservation and update item
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.delete(reservations).where(eq(reservations.itemId, itemId));
|
||||
|
||||
await tx
|
||||
.update(items)
|
||||
.set({ isReserved: false })
|
||||
.where(eq(items.id, itemId));
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
158
wishlist-app/src/routes/wishlist/[token]/+page.svelte
Normal file
158
wishlist-app/src/routes/wishlist/[token]/+page.svelte
Normal file
@@ -0,0 +1,158 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/components/ui/card';
|
||||
import { enhance } from '$app/forms';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let reservingItemId = $state<string | null>(null);
|
||||
let reserverNames = $state<Record<string, string>>({});
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 p-4 md:p-8">
|
||||
<div class="max-w-4xl mx-auto space-y-6">
|
||||
<!-- Header -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-3xl">{data.wishlist.title}</CardTitle>
|
||||
{#if data.wishlist.description}
|
||||
<CardDescription class="text-base">{data.wishlist.description}</CardDescription>
|
||||
{/if}
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<!-- Items List -->
|
||||
<div class="space-y-4">
|
||||
{#if data.wishlist.items && data.wishlist.items.length > 0}
|
||||
{#each data.wishlist.items as item}
|
||||
<Card>
|
||||
<CardContent class="p-6">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
{#if item.imageUrl}
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
class="w-full md:w-32 h-32 object-cover rounded-lg"
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div class="flex-1">
|
||||
<h3 class="font-semibold text-lg">{item.title}</h3>
|
||||
{#if item.priority === 'high'}
|
||||
<span
|
||||
class="inline-block px-2 py-1 text-xs font-medium bg-red-100 text-red-700 rounded mt-1"
|
||||
>
|
||||
High Priority
|
||||
</span>
|
||||
{:else if item.priority === 'low'}
|
||||
<span
|
||||
class="inline-block px-2 py-1 text-xs font-medium bg-gray-100 text-gray-700 rounded mt-1"
|
||||
>
|
||||
Low Priority
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if item.isReserved}
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<div class="text-sm text-green-600 font-medium">
|
||||
✓ Reserved
|
||||
{#if item.reservations?.[0]?.reserverName}
|
||||
by {item.reservations[0].reserverName}
|
||||
{/if}
|
||||
</div>
|
||||
<form method="POST" action="?/unreserve" use:enhance>
|
||||
<input type="hidden" name="itemId" value={item.id} />
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
Cancel Reservation
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
{:else if reservingItemId === item.id}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/reserve"
|
||||
use:enhance={() => {
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
reservingItemId = null;
|
||||
reserverNames[item.id] = '';
|
||||
};
|
||||
}}
|
||||
class="flex flex-col gap-2 w-full md:w-auto"
|
||||
>
|
||||
<input type="hidden" name="itemId" value={item.id} />
|
||||
<Input
|
||||
name="reserverName"
|
||||
placeholder="Your name (optional)"
|
||||
bind:value={reserverNames[item.id]}
|
||||
class="w-full md:w-48"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<Button type="submit" size="sm" class="flex-1">Confirm</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => (reservingItemId = null)}
|
||||
class="flex-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<Button
|
||||
onclick={() => (reservingItemId = item.id)}
|
||||
size="sm"
|
||||
class="w-full md:w-auto"
|
||||
>
|
||||
Reserve This
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if item.description}
|
||||
<p class="text-muted-foreground">{item.description}</p>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-wrap gap-4 text-sm">
|
||||
{#if item.price}
|
||||
<span class="font-medium">${item.price}</span>
|
||||
{/if}
|
||||
{#if item.link}
|
||||
<a
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary hover:underline"
|
||||
>
|
||||
View Product →
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/each}
|
||||
{:else}
|
||||
<Card>
|
||||
<CardContent class="p-12 text-center text-muted-foreground">
|
||||
<p>This wishlist doesn't have any items yet.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,80 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { wishlists, items } from '$lib/server/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const wishlist = await db.query.wishlists.findFirst({
|
||||
where: eq(wishlists.ownerToken, params.token),
|
||||
with: {
|
||||
items: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!wishlist) {
|
||||
throw error(404, 'Wishlist not found');
|
||||
}
|
||||
|
||||
return {
|
||||
wishlist,
|
||||
publicUrl: `/wishlist/${wishlist.publicToken}`
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
addItem: async ({ params, request }) => {
|
||||
const formData = await request.formData();
|
||||
const title = formData.get('title') as string;
|
||||
const description = formData.get('description') as string;
|
||||
const link = formData.get('link') as string;
|
||||
const imageUrl = formData.get('imageUrl') as string;
|
||||
const price = formData.get('price') as string;
|
||||
const priority = formData.get('priority') as 'high' | 'medium' | 'low';
|
||||
|
||||
if (!title?.trim()) {
|
||||
return { success: false, error: 'Title is required' };
|
||||
}
|
||||
|
||||
const wishlist = await db.query.wishlists.findFirst({
|
||||
where: eq(wishlists.ownerToken, params.token)
|
||||
});
|
||||
|
||||
if (!wishlist) {
|
||||
throw error(404, 'Wishlist not found');
|
||||
}
|
||||
|
||||
await db.insert(items).values({
|
||||
wishlistId: wishlist.id,
|
||||
title: title.trim(),
|
||||
description: description?.trim() || null,
|
||||
link: link?.trim() || null,
|
||||
imageUrl: imageUrl?.trim() || null,
|
||||
price: price ? price.trim() : null,
|
||||
priority: priority || 'medium'
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
deleteItem: async ({ params, request }) => {
|
||||
const formData = await request.formData();
|
||||
const itemId = formData.get('itemId') as string;
|
||||
|
||||
if (!itemId) {
|
||||
return { success: false, error: 'Item ID is required' };
|
||||
}
|
||||
|
||||
const wishlist = await db.query.wishlists.findFirst({
|
||||
where: eq(wishlists.ownerToken, params.token)
|
||||
});
|
||||
|
||||
if (!wishlist) {
|
||||
throw error(404, 'Wishlist not found');
|
||||
}
|
||||
|
||||
await db.delete(items).where(eq(items.id, itemId));
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
223
wishlist-app/src/routes/wishlist/[token]/edit/+page.svelte
Normal file
223
wishlist-app/src/routes/wishlist/[token]/edit/+page.svelte
Normal file
@@ -0,0 +1,223 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/components/ui/card';
|
||||
import { enhance } from '$app/forms';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let showAddForm = $state(false);
|
||||
let copiedPublicLink = $state(false);
|
||||
let copiedOwnerLink = $state(false);
|
||||
|
||||
const publicLink = $derived(
|
||||
typeof window !== 'undefined' ? `${window.location.origin}${data.publicUrl}` : ''
|
||||
);
|
||||
const ownerLink = $derived(typeof window !== 'undefined' ? window.location.href : '');
|
||||
|
||||
async function copyToClipboard(text: string, type: 'public' | 'owner') {
|
||||
await navigator.clipboard.writeText(text);
|
||||
if (type === 'public') {
|
||||
copiedPublicLink = true;
|
||||
setTimeout(() => (copiedPublicLink = false), 2000);
|
||||
} else {
|
||||
copiedOwnerLink = true;
|
||||
setTimeout(() => (copiedOwnerLink = false), 2000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 p-4 md:p-8">
|
||||
<div class="max-w-4xl mx-auto space-y-6">
|
||||
<!-- Header -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-3xl">{data.wishlist.title}</CardTitle>
|
||||
{#if data.wishlist.description}
|
||||
<CardDescription class="text-base">{data.wishlist.description}</CardDescription>
|
||||
{/if}
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>Share with friends (view only)</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input readonly value={publicLink} class="font-mono text-sm" />
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => copyToClipboard(publicLink, 'public')}
|
||||
>
|
||||
{copiedPublicLink ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label>Your edit link (keep this private!)</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input readonly value={ownerLink} class="font-mono text-sm" />
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => copyToClipboard(ownerLink, 'owner')}
|
||||
>
|
||||
{copiedOwnerLink ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- Add Item Button -->
|
||||
<div>
|
||||
<Button onclick={() => (showAddForm = !showAddForm)} class="w-full md:w-auto">
|
||||
{showAddForm ? 'Cancel' : '+ Add Item'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Add Item Form -->
|
||||
{#if showAddForm}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Add New Item</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/addItem"
|
||||
use:enhance={() => {
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
showAddForm = false;
|
||||
};
|
||||
}}
|
||||
class="space-y-4"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="title">Item Name *</Label>
|
||||
<Input id="title" name="title" required placeholder="e.g., Blue Headphones" />
|
||||
</div>
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="description">Description</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
name="description"
|
||||
placeholder="Add details about the item..."
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="link">Link (URL)</Label>
|
||||
<Input id="link" name="link" type="url" placeholder="https://..." />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="imageUrl">Image URL</Label>
|
||||
<Input id="imageUrl" name="imageUrl" type="url" placeholder="https://..." />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="price">Price</Label>
|
||||
<Input id="price" name="price" type="number" step="0.01" placeholder="0.00" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="priority">Priority</Label>
|
||||
<select
|
||||
id="priority"
|
||||
name="priority"
|
||||
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm"
|
||||
>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium" selected>Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" class="w-full md:w-auto">Add Item</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
<!-- Items List -->
|
||||
<div class="space-y-4">
|
||||
{#if data.wishlist.items && data.wishlist.items.length > 0}
|
||||
{#each data.wishlist.items as item}
|
||||
<Card>
|
||||
<CardContent class="p-6">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
{#if item.imageUrl}
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
class="w-full md:w-32 h-32 object-cover rounded-lg"
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg">{item.title}</h3>
|
||||
{#if item.priority === 'high'}
|
||||
<span
|
||||
class="inline-block px-2 py-1 text-xs font-medium bg-red-100 text-red-700 rounded"
|
||||
>
|
||||
High Priority
|
||||
</span>
|
||||
{:else if item.priority === 'low'}
|
||||
<span
|
||||
class="inline-block px-2 py-1 text-xs font-medium bg-gray-100 text-gray-700 rounded"
|
||||
>
|
||||
Low Priority
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<form method="POST" action="?/deleteItem" use:enhance>
|
||||
<input type="hidden" name="itemId" value={item.id} />
|
||||
<Button type="submit" variant="destructive" size="sm">
|
||||
Delete
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
{#if item.description}
|
||||
<p class="text-muted-foreground">{item.description}</p>
|
||||
{/if}
|
||||
<div class="flex flex-wrap gap-4 text-sm">
|
||||
{#if item.price}
|
||||
<span class="font-medium">${item.price}</span>
|
||||
{/if}
|
||||
{#if item.link}
|
||||
<a
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary hover:underline"
|
||||
>
|
||||
View Product →
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{#if item.isReserved}
|
||||
<div class="text-sm text-green-600 font-medium">
|
||||
✓ Someone has reserved this item
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/each}
|
||||
{:else}
|
||||
<Card>
|
||||
<CardContent class="p-12 text-center text-muted-foreground">
|
||||
<p>No items yet. Click "Add Item" to get started!</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
3
wishlist-app/static/robots.txt
Normal file
3
wishlist-app/static/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
17
wishlist-app/svelte.config.js
Normal file
17
wishlist-app/svelte.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://svelte.dev/docs/kit/integrations
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
out: 'build'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
20
wishlist-app/tsconfig.json
Normal file
20
wishlist-app/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
7
wishlist-app/vite.config.ts
Normal file
7
wishlist-app/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()]
|
||||
});
|
||||
Reference in New Issue
Block a user