# Monaco USA Portal 2026 - Complete Rebuild
## Project Overview
Rebuild the Monaco USA member portal from scratch in `monacousa-portal-2026/` with modern architecture, beautiful UI, and improved functionality.
---
# DETAILED FEATURE SPECIFICATIONS
## 1. MEMBER SYSTEM (Detailed)
### 1.1 Member ID Format
- **Format**: `MUSA-XXXX` (sequential 4-digit number)
- **Examples**: MUSA-0001, MUSA-0042, MUSA-1234
- **Auto-generated** on member creation
- **Immutable** once assigned
- **Unique constraint** in database
### 1.2 Membership Statuses (Admin-Configurable)
Admin can create, edit, and delete statuses via Settings.
**Default Statuses (seeded on first run):**
| Status | Color | Description | Is Default |
|--------|-------|-------------|------------|
| `pending` | Yellow | New member, awaiting dues payment | Yes (for new signups) |
| `active` | Green | Dues paid, full access | No |
| `inactive` | Gray | Lapsed membership or suspended | No |
| `expired` | Red | Membership terminated | No |
**Status Configuration Table:**
```sql
CREATE TABLE public.membership_statuses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#6b7280', -- Tailwind gray-500
description TEXT,
is_default BOOLEAN DEFAULT FALSE, -- Used for new signups
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### 1.3 Roles/Tiers
**Fixed 3-tier system (not configurable):**
| Role | Access Level | Capabilities |
|------|--------------|--------------|
| `member` | Basic | View own profile, events, pay dues |
| `board` | Elevated | + Member directory, record payments, manage events |
| `admin` | Full | + User management, system settings, all data |
### 1.4 Required Member Fields
All fields marked as required during signup:
| Field | Type | Validation | Notes |
|-------|------|------------|-------|
| `first_name` | Text | Min 2 chars | Required |
| `last_name` | Text | Min 2 chars | Required |
| `email` | Email | Valid email format | Required, unique |
| `phone` | Text | International format | Required |
| `date_of_birth` | Date | Must be 18+ years old | Required |
| `address` | Text | Min 10 chars | Required |
| `nationality` | Array | At least 1 country | Required, multiple allowed |
### 1.5 Optional Member Fields
| Field | Type | Notes |
|-------|------|-------|
| `avatar_url` | Text | Supabase Storage path |
| `membership_type_id` | UUID | Links to membership_types table |
| `notes` | Text | Admin-only notes about member |
### 1.6 Nationality Handling
- **Multiple nationalities allowed**
- Stored as PostgreSQL `TEXT[]` array
- Uses ISO 3166-1 alpha-2 country codes: `['FR', 'US', 'MC']`
- UI shows country flags + names
- Searchable/filterable in directory
### 1.7 Profile Features
- **Profile photo**: Upload via Supabase Storage
- Max size: 5MB
- Formats: JPG, PNG, WebP
- Auto-resized to 256x256
- Stored at: `avatars/{member_id}/profile.{ext}`
- **No bio field** (simplified profile)
- Members can edit: name, phone, address, nationality, photo
### 1.8 Member Directory
**Visibility controlled by admin settings:**
```sql
CREATE TABLE public.directory_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
field_name TEXT NOT NULL UNIQUE,
visible_to_members BOOLEAN DEFAULT FALSE,
visible_to_board BOOLEAN DEFAULT TRUE,
visible_to_admin BOOLEAN DEFAULT TRUE,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Default visibility settings
INSERT INTO directory_settings (field_name, visible_to_members, visible_to_board) VALUES
('first_name', true, true),
('last_name', true, true),
('avatar_url', true, true),
('nationality', true, true),
('email', false, true),
('phone', false, true),
('address', false, true),
('date_of_birth', false, true),
('member_since', true, true),
('membership_status', false, true);
```
### 1.9 Member Signup Flow
```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ /signup │────▶│ Create Auth │────▶│ Email Verify│
│ Form │ │ User + Member│ │ Link Sent │
└─────────────┘ └──────────────┘ └─────────────┘
│
▼
┌──────────────┐ ┌─────────────┐
│ Status = │────▶│ Wait for │
│ 'pending' │ │ Dues Payment│
└──────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Board/Admin │
│ Records Dues│
└─────────────┘
│
▼
┌─────────────┐
│ Status = │
│ 'active' │
└─────────────┘
```
**Key Points:**
- Email verification required
- Status starts as `pending`
- Member gains `active` status ONLY when first dues payment recorded
- Pending members can log in but see limited dashboard
### 1.10 Admin Member Management
**Two ways to add members:**
**Option A: Direct Add**
1. Admin fills out member form
2. Admin sets temporary password OR sends password setup email
3. Member record created with chosen status
4. Member can log in immediately
**Option B: Invite**
1. Admin enters email + basic info
2. System sends invitation email with signup link
3. Invitee completes signup form
4. Status set based on invite settings
### 1.11 Membership Types (Admin-Configurable)
Admin can create membership tiers with different pricing:
```sql
CREATE TABLE public.membership_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE, -- 'regular', 'student', 'senior'
display_name TEXT NOT NULL, -- 'Regular Member', 'Student'
annual_dues DECIMAL(10,2) NOT NULL, -- 50.00, 25.00, etc.
description TEXT,
is_default BOOLEAN DEFAULT FALSE, -- Default for new signups
is_active BOOLEAN DEFAULT TRUE, -- Can be assigned
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Default membership types
INSERT INTO membership_types (name, display_name, annual_dues, is_default) VALUES
('regular', 'Regular Member', 50.00, true),
('student', 'Student', 25.00, false),
('senior', 'Senior (65+)', 35.00, false),
('family', 'Family', 75.00, false),
('honorary', 'Honorary Member', 0.00, false);
```
### 1.12 Complete Member Schema
```sql
CREATE TABLE public.members (
-- Identity
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
member_id TEXT UNIQUE NOT NULL, -- MUSA-0001 format (auto-generated)
-- Required Personal Info
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
phone TEXT NOT NULL,
date_of_birth DATE NOT NULL,
address TEXT NOT NULL,
nationality TEXT[] NOT NULL DEFAULT '{}',
-- Membership
role TEXT NOT NULL DEFAULT 'member'
CHECK (role IN ('member', 'board', 'admin')),
membership_status_id UUID REFERENCES public.membership_statuses(id),
membership_type_id UUID REFERENCES public.membership_types(id),
member_since DATE DEFAULT CURRENT_DATE,
-- Profile
avatar_url TEXT,
-- Admin
notes TEXT, -- Admin-only notes
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Auto-generate member_id trigger
CREATE OR REPLACE FUNCTION generate_member_id()
RETURNS TRIGGER AS $$
DECLARE
next_num INTEGER;
BEGIN
SELECT COALESCE(MAX(CAST(SUBSTRING(member_id FROM 6) AS INTEGER)), 0) + 1
INTO next_num
FROM public.members;
NEW.member_id := 'MUSA-' || LPAD(next_num::TEXT, 4, '0');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_member_id
BEFORE INSERT ON public.members
FOR EACH ROW
WHEN (NEW.member_id IS NULL)
EXECUTE FUNCTION generate_member_id();
```
---
## 2. DUES/PAYMENTS SYSTEM (Detailed)
### 2.1 Dues Cycle
- **Due date calculation**: Payment date + 365 days
- **Example**: Payment on Jan 15, 2026 → Due Jan 15, 2027
- **No proration**: Full annual dues regardless of join date
### 2.2 Payment Methods
**Bank transfer only** (no online payments):
- IBAN tracking
- Reference number for matching
- Manual recording by Board/Admin
### 2.3 Payment Recording
**Who can record payments:**
- Board members
- Admins
**Standard payment data tracked:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `member_id` | UUID | Yes | Which member |
| `amount` | Decimal | Yes | Payment amount (€) |
| `payment_date` | Date | Yes | When payment was made |
| `due_date` | Date | Yes | When this payment period ends (auto-calculated) |
| `reference` | Text | No | Bank transfer reference |
| `payment_method` | Text | Yes | Always 'bank_transfer' for now |
| `recorded_by` | UUID | Yes | Board/Admin who recorded |
| `notes` | Text | No | Optional notes |
### 2.4 Dues Settings (Admin-Configurable)
```sql
CREATE TABLE public.dues_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
setting_key TEXT UNIQUE NOT NULL,
setting_value TEXT NOT NULL,
description TEXT,
updated_at TIMESTAMPTZ DEFAULT NOW(),
updated_by UUID REFERENCES public.members(id)
);
-- Default settings
INSERT INTO dues_settings (setting_key, setting_value, description) VALUES
('reminder_days_before', '30,7', 'Days before due date to send reminders (comma-separated)'),
('grace_period_days', '30', 'Days after due date before auto-inactive'),
('overdue_reminder_interval', '14', 'Days between overdue reminder emails'),
('payment_iban', 'MC58 1756 9000 0104 0050 1001 860', 'IBAN for dues payment'),
('payment_account_holder', 'ASSOCIATION MONACO USA', 'Account holder name'),
('payment_instructions', 'Please include your Member ID in the reference', 'Payment instructions');
```
### 2.5 Automatic Reminders
**Reminder Schedule (configurable via settings):**
1. **30 days before** due date: "Your dues are coming up"
2. **7 days before** due date: "Reminder: dues due in 1 week"
3. **On due date**: "Your dues are now due"
4. **Every 14 days overdue**: "Your dues are overdue" (until grace period ends)
**Email Content Includes:**
- Member name
- Amount due (from membership_type)
- Due date
- IBAN and account holder
- Payment reference suggestion (Member ID)
- Link to portal
**Technical Implementation:**
- Supabase Edge Function runs daily
- Checks all members for reminder triggers
- Logs sent emails in `email_logs` table
- Respects settings for intervals
### 2.6 Overdue Handling
**Grace Period Flow:**
```
Due Date Passed
│
▼
┌─────────────────────────────────────────┐
│ GRACE PERIOD (configurable, default 30 days) │
│ - Status remains 'active' │
│ - Overdue reminders sent │
│ - Flagged in dashboard │
└─────────────────────────────────────────┘
│
▼ (grace period ends)
┌─────────────────────────────────────────┐
│ AUTO STATUS CHANGE │
│ - Status → 'inactive' │
│ - Final notification email │
│ - Member loses active access │
└─────────────────────────────────────────┘
```
**Supabase Edge Function for Auto-Update:**
```typescript
// Runs daily via cron
async function updateOverdueMembers() {
const gracePeriodDays = await getSetting('grace_period_days');
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - gracePeriodDays);
// Find members past grace period
const { data: overdueMembers } = await supabase
.from('members_with_dues')
.select('*')
.eq('membership_status', 'active')
.lt('current_due_date', cutoffDate.toISOString());
// Update each to inactive
for (const member of overdueMembers) {
await supabase
.from('members')
.update({ membership_status_id: inactiveStatusId })
.eq('id', member.id);
// Send final notification
await sendEmail(member.email, 'membership_lapsed', { ... });
}
}
```
### 2.7 Payment History (Member Visible)
Members can see their complete payment history:
**Display includes:**
- Payment date
- Amount paid
- Due date (period covered)
- Reference number
- Payment method
**Members CANNOT see:**
- Who recorded the payment
- Internal notes
- Other members' payments
### 2.8 Dues Dashboard (Board/Admin)
**Overview Stats:**
- Total members with current dues
- Members with dues due soon (next 30 days)
- Overdue members count
- Total collected this year
**Filterable Member List:**
- Filter by: status (current, due soon, overdue, never paid)
- Sort by: due date, days overdue, member name
- Quick actions: Record payment, Send reminder
**Individual Member View:**
- Full payment history
- Current dues status
- Quick record payment form
- Send manual reminder button
### 2.9 Complete Dues Schema
```sql
-- Dues payments table
CREATE TABLE public.dues_payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
member_id UUID NOT NULL REFERENCES public.members(id) ON DELETE CASCADE,
amount DECIMAL(10,2) NOT NULL,
currency TEXT DEFAULT 'EUR',
payment_date DATE NOT NULL,
due_date DATE NOT NULL, -- Calculated: payment_date + 1 year
payment_method TEXT DEFAULT 'bank_transfer',
reference TEXT, -- Bank transfer reference
notes TEXT, -- Internal notes
recorded_by UUID NOT NULL REFERENCES public.members(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Trigger to auto-calculate due_date
CREATE OR REPLACE FUNCTION calculate_due_date()
RETURNS TRIGGER AS $$
BEGIN
NEW.due_date := NEW.payment_date + INTERVAL '1 year';
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_due_date
BEFORE INSERT ON public.dues_payments
FOR EACH ROW
WHEN (NEW.due_date IS NULL)
EXECUTE FUNCTION calculate_due_date();
-- After payment: update member status to active
CREATE OR REPLACE FUNCTION update_member_status_on_payment()
RETURNS TRIGGER AS $$
DECLARE
active_status_id UUID;
BEGIN
-- Get active status ID
SELECT id INTO active_status_id
FROM public.membership_statuses
WHERE name = 'active';
-- Update member status
UPDATE public.members
SET membership_status_id = active_status_id,
updated_at = NOW()
WHERE id = NEW.member_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER activate_member_on_payment
AFTER INSERT ON public.dues_payments
FOR EACH ROW
EXECUTE FUNCTION update_member_status_on_payment();
-- Computed view for dues status
CREATE VIEW public.members_with_dues AS
SELECT
m.*,
ms.name as status_name,
ms.display_name as status_display_name,
ms.color as status_color,
mt.display_name as membership_type_name,
mt.annual_dues,
dp.last_payment_date,
dp.current_due_date,
CASE
WHEN dp.current_due_date IS NULL THEN 'never_paid'
WHEN dp.current_due_date < CURRENT_DATE THEN 'overdue'
WHEN dp.current_due_date < CURRENT_DATE + INTERVAL '30 days' THEN 'due_soon'
ELSE 'current'
END as dues_status,
CASE
WHEN dp.current_due_date < CURRENT_DATE
THEN (CURRENT_DATE - dp.current_due_date)::INTEGER
ELSE NULL
END as days_overdue,
CASE
WHEN dp.current_due_date >= CURRENT_DATE
THEN (dp.current_due_date - CURRENT_DATE)::INTEGER
ELSE NULL
END as days_until_due
FROM public.members m
LEFT JOIN public.membership_statuses ms ON m.membership_status_id = ms.id
LEFT JOIN public.membership_types mt ON m.membership_type_id = mt.id
LEFT JOIN LATERAL (
SELECT
payment_date as last_payment_date,
due_date as current_due_date
FROM public.dues_payments
WHERE member_id = m.id
ORDER BY due_date DESC
LIMIT 1
) dp ON true;
```
### 2.10 Email Templates for Dues
**Types:**
1. `dues_reminder` - Upcoming dues reminder
2. `dues_due_today` - Dues due today
3. `dues_overdue` - Overdue reminder
4. `dues_lapsed` - Membership lapsed (grace period ended)
5. `dues_received` - Payment confirmation
**Template Variables:**
- `{{member_name}}` - Full name
- `{{member_id}}` - MUSA-XXXX
- `{{amount}}` - Due amount
- `{{due_date}}` - Formatted date
- `{{days_until_due}}` or `{{days_overdue}}`
- `{{iban}}` - Payment IBAN
- `{{account_holder}}` - Account name
- `{{portal_link}}` - Link to portal
---
## 3. EVENTS SYSTEM (Detailed)
### 3.1 Event Types (Admin-Configurable)
```sql
CREATE TABLE public.event_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#3b82f6', -- Tailwind blue-500
icon TEXT, -- Lucide icon name
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Default event types
INSERT INTO event_types (name, display_name, color, icon) VALUES
('social', 'Social Event', '#10b981', 'party-popper'),
('meeting', 'Meeting', '#6366f1', 'users'),
('fundraiser', 'Fundraiser', '#f59e0b', 'heart-handshake'),
('workshop', 'Workshop', '#8b5cf6', 'graduation-cap'),
('gala', 'Gala/Formal', '#ec4899', 'sparkles'),
('other', 'Other', '#6b7280', 'calendar');
```
### 3.2 Event Visibility
**Visibility Options:**
| Level | Who Can See | Description |
|-------|-------------|-------------|
| `public` | Anyone | Visible on public events page (no login) |
| `members` | All logged-in members | Default for most events |
| `board` | Board + Admin only | Board meetings, internal events |
| `admin` | Admin only | Administrative events |
### 3.3 Event Pricing
**Pricing Model:**
- Each event can be free or paid
- Paid events have **member price** and **non-member price**
- Member pricing determined by `membership_type_id` (if tiered pricing enabled)
- Non-members pay non-member price always
**Pricing Fields:**
```sql
is_paid BOOLEAN DEFAULT FALSE,
member_price DECIMAL(10,2) DEFAULT 0,
non_member_price DECIMAL(10,2) DEFAULT 0,
pricing_notes TEXT -- "Includes dinner and drinks"
```
### 3.4 Guest/+1 Handling
**Per-Event Configuration:**
- `max_guests_per_member` - 0, 1, 2, 3, or unlimited
- Each RSVP tracks guest count and guest names
- Guests count toward total capacity
- Non-members can bring guests too (if enabled)
### 3.5 Non-Member (Public) RSVP
**Flow for public events:**
```
┌─────────────────┐ ┌──────────────────┐
│ Public Events │────▶│ Event Detail │
│ Page (no login) │ │ (public visible) │
└─────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐
│ RSVP Form │
│ (no account) │
│ - Name │
│ - Email │
│ - Phone │
│ - Guest count │
│ - Guest names │
└──────────────────┘
│
▼
┌──────────────────┐
│ Payment Info │
│ (if paid event) │
│ - IBAN shown │
│ - Reference # │
└──────────────────┘
│
▼
┌──────────────────┐
│ RSVP Confirmed │
│ (pending payment)│
│ Email sent │
└──────────────────┘
```
**Non-Member RSVP Table:**
```sql
CREATE TABLE public.event_rsvps_public (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES public.events(id) ON DELETE CASCADE,
-- Contact info (required)
full_name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
-- RSVP details
status TEXT NOT NULL DEFAULT 'confirmed'
CHECK (status IN ('confirmed', 'declined', 'maybe', 'waitlist', 'cancelled')),
guest_count INTEGER DEFAULT 0,
guest_names TEXT[],
-- Payment (for paid events)
payment_status TEXT DEFAULT 'not_required'
CHECK (payment_status IN ('not_required', 'pending', 'paid')),
payment_reference TEXT,
payment_amount DECIMAL(10,2),
-- Attendance
attended BOOLEAN DEFAULT FALSE,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(event_id, email) -- One RSVP per email per event
);
```
### 3.6 RSVP Status Options
**For Members and Non-Members:**
| Status | Description |
|--------|-------------|
| `confirmed` | Attending the event |
| `declined` | Not attending |
| `maybe` | Tentative/undecided |
| `waitlist` | Event full, on waitlist |
| `cancelled` | Cancelled RSVP |
### 3.7 Capacity & Waitlist
**Capacity Management:**
- `max_attendees` - Total spots (null = unlimited)
- Includes members + guests + non-members + their guests
- When full, new RSVPs go to waitlist
**Auto-Promote Waitlist:**
```typescript
// Trigger when RSVP is cancelled or declined
async function promoteFromWaitlist(eventId: string) {
// Get event capacity
const event = await getEvent(eventId);
const currentCount = await getCurrentAttendeeCount(eventId);
if (event.max_attendees && currentCount >= event.max_attendees) {
return; // Still full
}
// Get oldest waitlist entry
const waitlisted = await supabase
.from('event_rsvps')
.select('*')
.eq('event_id', eventId)
.eq('status', 'waitlist')
.order('created_at', { ascending: true })
.limit(1)
.single();
if (waitlisted) {
// Promote to confirmed
await supabase
.from('event_rsvps')
.update({ status: 'confirmed' })
.eq('id', waitlisted.id);
// Send notification email
await sendEmail(waitlisted.member.email, 'waitlist_promoted', {
event_title: event.title,
event_date: event.start_datetime
});
}
}
```
### 3.8 Attendance Tracking
**Check-in System:**
- Board/Admin can mark attendance after event
- Checkbox per RSVP: attended yes/no
- Track attendance rate per event
- Member attendance history viewable
```sql
-- Add to RSVPs
attended BOOLEAN DEFAULT FALSE,
checked_in_at TIMESTAMPTZ,
checked_in_by UUID REFERENCES public.members(id)
```
### 3.9 Calendar Views
**Available Views:**
1. **Month** - Traditional calendar grid
2. **Week** - Weekly schedule view
3. **Day** - Single day detailed view
4. **List** - Upcoming events list
**Using FullCalendar (SvelteKit compatible):**
```typescript
import Calendar from '@event-calendar/core';
import TimeGrid from '@event-calendar/time-grid';
import DayGrid from '@event-calendar/day-grid';
import List from '@event-calendar/list';
```
### 3.10 Event Schema
```sql
CREATE TABLE public.events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Basic Info
title TEXT NOT NULL,
description TEXT,
event_type_id UUID REFERENCES public.event_types(id),
-- Date/Time
start_datetime TIMESTAMPTZ NOT NULL,
end_datetime TIMESTAMPTZ NOT NULL,
all_day BOOLEAN DEFAULT FALSE,
timezone TEXT DEFAULT 'Europe/Monaco',
-- Location
location TEXT,
location_url TEXT, -- Google Maps link, etc.
-- Capacity
max_attendees INTEGER, -- null = unlimited
max_guests_per_member INTEGER DEFAULT 1,
-- Pricing
is_paid BOOLEAN DEFAULT FALSE,
member_price DECIMAL(10,2) DEFAULT 0,
non_member_price DECIMAL(10,2) DEFAULT 0,
pricing_notes TEXT,
-- Visibility
visibility TEXT NOT NULL DEFAULT 'members'
CHECK (visibility IN ('public', 'members', 'board', 'admin')),
-- Status
status TEXT NOT NULL DEFAULT 'published'
CHECK (status IN ('draft', 'published', 'cancelled', 'completed')),
-- Media
cover_image_url TEXT, -- Event banner/cover image
-- Meta
created_by UUID NOT NULL REFERENCES public.members(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Member RSVPs
CREATE TABLE public.event_rsvps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES public.events(id) ON DELETE CASCADE,
member_id UUID NOT NULL REFERENCES public.members(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'confirmed'
CHECK (status IN ('confirmed', 'declined', 'maybe', 'waitlist', 'cancelled')),
guest_count INTEGER DEFAULT 0,
guest_names TEXT[],
notes TEXT,
-- Payment (for paid events)
payment_status TEXT DEFAULT 'not_required'
CHECK (payment_status IN ('not_required', 'pending', 'paid')),
payment_reference TEXT,
payment_amount DECIMAL(10,2),
-- Attendance
attended BOOLEAN DEFAULT FALSE,
checked_in_at TIMESTAMPTZ,
checked_in_by UUID REFERENCES public.members(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(event_id, member_id)
);
-- View for event with counts
CREATE VIEW public.events_with_counts AS
SELECT
e.*,
et.display_name as event_type_name,
et.color as event_type_color,
et.icon as event_type_icon,
COALESCE(member_rsvps.confirmed_count, 0) +
COALESCE(member_rsvps.guest_count, 0) +
COALESCE(public_rsvps.confirmed_count, 0) +
COALESCE(public_rsvps.guest_count, 0) as total_attendees,
COALESCE(member_rsvps.confirmed_count, 0) as member_count,
COALESCE(public_rsvps.confirmed_count, 0) as non_member_count,
COALESCE(member_rsvps.waitlist_count, 0) +
COALESCE(public_rsvps.waitlist_count, 0) as waitlist_count,
CASE
WHEN e.max_attendees IS NULL THEN FALSE
WHEN (COALESCE(member_rsvps.confirmed_count, 0) +
COALESCE(member_rsvps.guest_count, 0) +
COALESCE(public_rsvps.confirmed_count, 0) +
COALESCE(public_rsvps.guest_count, 0)) >= e.max_attendees THEN TRUE
ELSE FALSE
END as is_full
FROM public.events e
LEFT JOIN public.event_types et ON e.event_type_id = et.id
LEFT JOIN LATERAL (
SELECT
COUNT(*) FILTER (WHERE status = 'confirmed') as confirmed_count,
COALESCE(SUM(guest_count) FILTER (WHERE status = 'confirmed'), 0) as guest_count,
COUNT(*) FILTER (WHERE status = 'waitlist') as waitlist_count
FROM public.event_rsvps
WHERE event_id = e.id
) member_rsvps ON true
LEFT JOIN LATERAL (
SELECT
COUNT(*) FILTER (WHERE status = 'confirmed') as confirmed_count,
COALESCE(SUM(guest_count) FILTER (WHERE status = 'confirmed'), 0) as guest_count,
COUNT(*) FILTER (WHERE status = 'waitlist') as waitlist_count
FROM public.event_rsvps_public
WHERE event_id = e.id
) public_rsvps ON true;
```
### 3.11 Event Permissions
| Action | Member | Board | Admin |
|--------|--------|-------|-------|
| View public events | - | - | - |
| View member events | ✓ | ✓ | ✓ |
| View board events | - | ✓ | ✓ |
| View admin events | - | - | ✓ |
| RSVP to events | ✓ | ✓ | ✓ |
| Create events | - | ✓ | ✓ |
| Edit own events | - | ✓ | ✓ |
| Edit any event | - | - | ✓ |
| Delete events | - | - | ✓ |
| Manage RSVPs | - | ✓ | ✓ |
| Track attendance | - | ✓ | ✓ |
### 3.12 Event Email Notifications
**Email Types:**
1. `event_created` - New event announcement (for public/member events)
2. `event_reminder` - Reminder before event (configurable: 1 day, 1 hour)
3. `event_updated` - Event details changed
4. `event_cancelled` - Event cancelled
5. `rsvp_confirmation` - RSVP received
6. `waitlist_promoted` - Promoted from waitlist
7. `event_payment_reminder` - Payment reminder for paid events
**Template Variables:**
- `{{event_title}}`, `{{event_date}}`, `{{event_time}}`
- `{{event_location}}`, `{{event_description}}`
- `{{member_name}}`, `{{guest_count}}`
- `{{payment_amount}}`, `{{payment_iban}}`
- `{{rsvp_status}}`, `{{portal_link}}`
---
## 4. AUTH & DASHBOARDS (Detailed)
### 4.1 Authentication Method
**Email/Password only** (no social login):
- Standard email + password signup/login
- Email verification required
- Password reset via email
- Remember me option (extended session)
### 4.2 Login Page Design
**Branded login with:**
- Monaco USA logo
- Association tagline
- Login form (email, password, remember me)
- Links: Forgot password, Sign up
- Glass-morphism styling
- Responsive (mobile-friendly)
### 4.3 Auth Flow
```
┌──────────────────────────────────────────────────────────────┐
│ SIGNUP FLOW │
├──────────────────────────────────────────────────────────────┤
│ /signup │
│ ├── Full form (all required fields) │
│ ├── Supabase Auth: signUp(email, password) │
│ ├── Create member record (status: pending) │
│ ├── Send verification email │
│ └── Show "Check your email" message │
│ │
│ /auth/callback (email verification link) │
│ ├── Verify email token │
│ ├── Update email_verified = true │
│ └── Redirect to /login with success message │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ LOGIN FLOW │
├──────────────────────────────────────────────────────────────┤
│ /login │
│ ├── Email + Password form │
│ ├── Supabase Auth: signInWithPassword() │
│ ├── Set session cookie (via Supabase SSR) │
│ ├── Fetch member record │
│ └── Redirect to /dashboard │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ PASSWORD RESET │
├──────────────────────────────────────────────────────────────┤
│ /forgot-password │
│ ├── Email input form │
│ ├── Supabase Auth: resetPasswordForEmail() │
│ └── Show "Check your email" message │
│ │
│ /auth/reset-password (from email link) │
│ ├── New password form │
│ ├── Supabase Auth: updateUser({ password }) │
│ └── Redirect to /login with success │
└──────────────────────────────────────────────────────────────┘
```
### 4.4 Session Management
**Supabase SSR Configuration:**
```typescript
// src/hooks.server.ts
export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createServerClient(
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll: () => event.cookies.getAll(),
setAll: (cookies) => cookies.forEach(({ name, value, options }) =>
event.cookies.set(name, value, { ...options, path: '/' })
)
}
}
);
event.locals.safeGetSession = async () => {
const { data: { session } } = await event.locals.supabase.auth.getSession();
if (!session) return { session: null, user: null, member: null };
const { data: { user } } = await event.locals.supabase.auth.getUser();
if (!user) return { session: null, user: null, member: null };
// Fetch member record
const { data: member } = await event.locals.supabase
.from('members_with_dues')
.select('*')
.eq('id', user.id)
.single();
return { session, user, member };
};
return resolve(event);
};
```
### 4.5 Navigation Structure
**Desktop: Collapsible Sidebar**
```
┌─────────────────────────────────────────────────────┐
│ ┌─────┐ │
│ │ │ Dashboard │
│ │LOGO │ ───────────────────────────────────── │
│ │ │ │
│ └─────┘ [Sidebar Navigation] [Content] │
│ │
│ 📊 Dashboard │
│ 👤 My Profile │
│ 📅 Events │
│ 💳 Payments │
│ │
│ ── Board ──────── (if board/admin) │
│ 👥 Members │
│ 📋 Dues Management │
│ 📅 Event Management │
│ │
│ ── Admin ──────── (if admin) │
│ ⚙️ Settings │
│ 👥 User Management │
│ 📄 Documents │
│ │
│ ───────────────── │
│ 🚪 Logout │
└─────────────────────────────────────────────────────┘
```
**Mobile: Bottom Navigation Bar**
```
┌─────────────────────────────────────┐
│ │
│ [Main Content] │
│ │
│ │
├─────────────────────────────────────┤
│ 🏠 📅 👤 ⚙️ ☰ │
│ Home Events Profile Settings More │
└─────────────────────────────────────┘
```
### 4.6 Unified Dashboard with Role Sections
**Single `/dashboard` route with role-based sections:**
```svelte
Dear {{member_name}}, Thank you for joining Monaco USA! Your Member ID is {{member_id}}. To complete your membership, please pay your annual dues of €{{dues_amount}}. Best regards, Dear {{member_name}}, This is a friendly reminder that your Monaco USA membership dues of €{{dues_amount}} are due on {{due_date}} ({{days_until_due}} days from now). Thank you for being a valued member! Dear {{member_name}}, Your Monaco USA membership dues of €{{dues_amount}} are now {{days_overdue}} days overdue. Please make your payment as soon as possible to maintain your membership benefits. Note: You have {{grace_period_remaining}} days remaining in your grace period before your membership is set to inactive. Dear {{member_name}}, Thank you! We have received your membership dues payment. Your membership is now active until {{next_due_date}}. Dear {{member_name}}, Your RSVP for {{event_title}} has been confirmed. Total: €{{total_amount}} Dear {{member_name}}, This is a reminder that {{event_title}} is {{time_until_event}}. {{event_description}} We look forward to seeing you there!
Welcome to Monaco USA!
Payment Details:
Monaco USA TeamDues Reminder
Payment Details:
Payment Overdue
Payment Details:
Payment Received!
Payment Details:
You''re Registered!
Event Details:
{{#if is_paid}}
Payment Required:
{{/if}}
Event Reminder
Event Details:
{{#if event_description}}
Dues Reminder
Dear {{member_nam │ ┌─────────────────────────────┐ │ │
│ │ e}},
This is a friendl │ │ │ │ │ │ │ y reminder that your │ │ Dear John Doe, │ │ │ │ │ Monaco USA membershi │ │ │ │ │ │ │ p dues...
│ │ This is a friendly │ │ │ │ │ ... │ │ reminder that your Monaco │ │ │ │ │ │ │ USA membership dues of │ │ │ │ │ │ │ €50.00 are due on... │ │ │ │ │ │ └─────────────────────────────┘ │ │ │ └──────────────────────┴───────────────────────────────────┘ │ │ │ │ AVAILABLE VARIABLES: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ {{member_name}} {{member_id}} {{dues_amount}} {{due_date}}│ │ │ │ {{days_until_due}} {{iban}} {{account_holder}} │ │ │ │ {{portal_link}} │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ [Cancel] [Send Test Email] [Save Changes] │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` ### 7.10 Email Logs View (Admin) ``` ┌──────────────────────────────────────────────────────────────────┐ │ Email Logs │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ Filter: [All Types ▼] [All Status ▼] [Last 30 days ▼] [Search] │ │ │ │ ┌────────┬────────────────┬──────────────┬────────┬──────────┐ │ │ │ Date │ Recipient │ Subject │ Type │ Status │ │ │ ├────────┼────────────────┼──────────────┼────────┼──────────┤ │ │ │ Jan 9 │ john@email.com │ Your Monaco │ dues_ │ 📬 Opened │ │ │ │ 14:30 │ John Doe │ USA dues... │ remind │ │ │ │ ├────────┼────────────────┼──────────────┼────────┼──────────┤ │ │ │ Jan 9 │ jane@email.com │ You're regis │ rsvp_ │ ✅ Sent │ │ │ │ 10:15 │ Jane Smith │ tered: Gala │ conf │ │ │ │ ├────────┼────────────────┼──────────────┼────────┼──────────┤ │ │ │ Jan 8 │ bob@email.com │ OVERDUE: You │ dues_ │ 🔴 Bounce │ │ │ │ 09:00 │ Bob Wilson │ r Monaco... │ overdu │ │ │ │ └────────┴────────────────┴──────────────┴────────┴──────────┘ │ │ │ │ Status Legend: │ │ ✅ Sent | 📬 Opened | 🔗 Clicked | 🔴 Bounced | ❌ Failed │ │ │ │ Stats: 156 sent this month | 78% open rate | 2 bounces │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` ### 7.11 Manual Broadcast Feature (Admin) **Admin can send broadcast emails to selected members:** ``` ┌──────────────────────────────────────────────────────────────────┐ │ Send Broadcast Email │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ Recipients: │ │ ○ All active members (45) │ │ ○ All members (52) │ │ ○ Board members only (5) │ │ ● Select specific members │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 🔍 Search members... │ │ │ ├──────────────────────────────────────────────────────────┤ │ │ │ ☑️ John Doe (john@email.com) │ │ │ │ ☑️ Jane Smith (jane@email.com) │ │ │ │ ☐ Bob Wilson (bob@email.com) │ │ │ │ ... │ │ │ └──────────────────────────────────────────────────────────┘ │ │ Selected: 2 members │ │ │ │ Subject: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Important Update from Monaco USA │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ Message: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ [Rich text editor with formatting options] │ │ │ │ │ │ │ │ Dear {{member_name}}, │ │ │ │ │ │ │ │ We wanted to inform you about... │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ [Preview] [Send Test to Myself] [Send to 2 Recipients] │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` ### 7.12 Email Cron Jobs (pg_cron in Supabase) ```sql -- Enable pg_cron extension CREATE EXTENSION IF NOT EXISTS pg_cron; -- Schedule daily email checks (runs at 9 AM Monaco time) SELECT cron.schedule( 'daily-email-scheduler', '0 9 * * *', -- Every day at 9:00 AM $$ SELECT net.http_post( url := 'https://your-project.supabase.co/functions/v1/email-scheduler', headers := '{"Authorization": "Bearer ' || current_setting('app.service_role_key') || '"}'::jsonb, body := '{}'::jsonb ); $$ ); -- Schedule event reminders (runs every hour) SELECT cron.schedule( 'hourly-event-reminders', '0 * * * *', -- Every hour $$ SELECT net.http_post( url := 'https://your-project.supabase.co/functions/v1/event-reminders', headers := '{"Authorization": "Bearer ' || current_setting('app.service_role_key') || '"}'::jsonb, body := '{}'::jsonb ); $$ ); ``` ### 7.13 Email Permissions | Action | Member | Board | Admin | |--------|--------|-------|-------| | Receive automated emails | ✓ | ✓ | ✓ | | View own email history | ✓ | ✓ | ✓ | | View all email logs | - | - | ✓ | | Edit email templates | - | - | ✓ | | Send broadcast emails | - | - | ✓ | | Send manual reminders | - | ✓ | ✓ | | Configure email settings | - | - | ✓ | | Test email connection | - | - | ✓ | --- ## Phase 1: Current System Analysis (COMPLETED) ### Current Tech Stack | Layer | Technology | |-------|------------| | Framework | Nuxt 3 (Vue 3) - SSR disabled, CSR-only | | UI Components | Vuetify 3 + Tailwind CSS + Custom SCSS | | Database | NocoDB (REST API over PostgreSQL) | | Authentication | Keycloak (OAuth2/OIDC) | | File Storage | MinIO (S3-compatible) | | Email | Nodemailer + Handlebars templates | | State | Vue Composition API (no Pinia) | ### Current Features Inventory #### 1. Member Management - **Data Model**: Member with 20+ fields (name, email, phone, DOB, nationality, address, etc.) - **Member ID Format**: `MUSA-YYYY-XXXX` (auto-generated) - **Status States**: Active, Inactive, Pending, Expired - **Portal Tiers**: admin, board, user - **Profile Images**: Stored in MinIO #### 2. Dues/Subscription System - **Calculation**: Due 1 year after last payment - **Fields Tracked**: `membership_date_paid`, `payment_due_date`, `current_year_dues_paid` - **Reminders**: 30-day advance warning, overdue notifications - **Auto-Status**: Members 1+ year overdue marked Inactive - **Rates**: €50 regular, €25 student, €35 senior, €75 family, €200 corporate #### 3. Events System - **Event Types**: meeting, social, fundraiser, workshop, board-only - **RSVP System**: confirmed, declined, pending, waitlist - **Guest Management**: Extra guests per RSVP - **Pricing**: Member vs non-member pricing - **Visibility**: public, board-only, admin-only - **Calendar**: FullCalendar integration with iCal feed #### 4. Authentication & Authorization - **Login Methods**: OAuth (Keycloak) + Direct login (ROPC) - **Role System**: Keycloak realm roles (monaco-admin, monaco-board, monaco-user) - **Session**: Server-side with HTTP-only cookies (7-30 days) - **Rate Limiting**: 5 attempts/15min, 1-hour IP block - **Signup Flow**: Form → reCAPTCHA → Keycloak user → NocoDB member → Verification email #### 5. Three Dashboard Types - **Admin**: Full system control, user management, settings, all features - **Board**: Member directory, dues management, events, meetings, governance - **Member**: Personal profile, events, payments, resources ### Current Pain Points (to address in rebuild) 1. CSR-only limits SEO and initial load performance 2. NocoDB adds complexity vs direct database access 3. String booleans ("true"/"false") cause type issues 4. No payment history table (only last payment tracked) 5. Vuetify + Tailwind overlap creates CSS conflicts 6. Large monolithic layout files (700-800+ lines each) --- ## Phase 2: New System Requirements ### Core Requirements (confirmed) - [x] Beautiful, modern, responsive frontend (not generic Vue look) - [x] Member tracking with subscription/dues management - [x] Dues due 1 year after last payment - [x] Event calendar with RSVP system - [x] Board members can create/manage events - [x] Event features: capacity, +1 guests, member/non-member pricing - [x] Three dashboard types: Admin, Board, Member - [x] Signup system similar to current - [x] **Manual payment tracking only** (no Stripe integration) - [x] **Email notifications** (dues reminders, event updates) - [x] **Document storage** (meeting minutes, governance docs) ### Deployment Strategy - **Replace entirely** - Switch over when ready (no parallel systems) - **Manual data entry** - Members will be entered manually (no migration scripts) --- ## FRONTEND FRAMEWORK OPTIONS ### Tier 1: Modern & Distinctive (Recommended) #### 1. **Qwik + QwikCity** ⭐ MOST INNOVATIVE | Aspect | Details | |--------|---------| | **What it is** | Resumable framework - HTML loads instantly, JS loads on-demand | | **Unique Feature** | Zero hydration - fastest possible load times | | **Learning Curve** | Medium (JSX-like but different mental model) | | **Ecosystem** | Growing - Auth.js, Drizzle, Modular Forms integrations | | **Benchmark Score** | 93.8 (highest among all frameworks) | **Pros:** - Blazing fast initial load (no hydration delay) - Feels like React/JSX but with better performance model - Built-in form handling with Zod validation - Server functions with `"use server"` directive - Excellent TypeScript support - Unique - won't look like every other site **Cons:** - Smaller community than React/Vue - Fewer pre-built component libraries - Newer framework (less battle-tested in production) - Some patterns feel unfamiliar at first **Best For:** Performance-focused apps where first impression matters --- #### 2. **SolidStart (Solid.js)** ⭐ MOST PERFORMANT REACTIVITY | Aspect | Details | |--------|---------| | **What it is** | Fine-grained reactive framework with meta-framework | | **Unique Feature** | No Virtual DOM - direct DOM updates via signals | | **Learning Curve** | Medium (React-like JSX, different reactivity) | | **Ecosystem** | Good - Ark UI, Kobalte for components | | **Benchmark Score** | 92.2 | **Pros:** - Smallest bundle sizes in the industry - React-like syntax (easy transition) - True reactivity (no re-renders, just updates) - Server functions and data loading built-in - Growing rapidly in popularity - Unique performance characteristics **Cons:** - Smaller ecosystem than React - Fewer tutorials and resources - Some React patterns don't translate directly - Component libraries less mature **Best For:** Highly interactive dashboards with lots of real-time updates --- #### 3. **SvelteKit** ⭐ BEST DEVELOPER EXPERIENCE | Aspect | Details | |--------|---------| | **What it is** | Compiler-based framework with full-stack capabilities | | **Unique Feature** | No virtual DOM, compiles to vanilla JS | | **Learning Curve** | Low (closest to vanilla HTML/CSS/JS) | | **Ecosystem** | Strong - Skeleton UI, Melt UI, Shadcn-Svelte | | **Benchmark Score** | 91.0 | **Pros:** - Simplest syntax - looks like enhanced HTML - Smallest learning curve - Excellent built-in animations/transitions - Strong TypeScript integration - Great form handling - Active, helpful community - Svelte 5 runes make state even simpler **Cons:** - Different mental model from React/Vue - Smaller job market (if that matters) - Some advanced patterns less documented - Breaking changes between Svelte 4 and 5 **Best For:** Clean, maintainable code with minimal boilerplate --- #### 4. **Astro + React/Vue/Svelte Islands** ⭐ MOST FLEXIBLE | Aspect | Details | |--------|---------| | **What it is** | Content-focused framework with "islands" of interactivity | | **Unique Feature** | Mix multiple frameworks, zero JS by default | | **Learning Curve** | Low-Medium | | **Ecosystem** | Excellent - use ANY UI library | | **Benchmark Score** | 90.2 | **Pros:** - Use React, Vue, Svelte, or Solid components together - Zero JavaScript shipped by default - Excellent for content + interactive sections - Built-in image optimization - Great Supabase integration documented - View Transitions API support **Cons:** - Not ideal for highly interactive SPAs - Island architecture adds complexity - More configuration for full interactivity - Less unified than single-framework approach **Best For:** Marketing site + member portal hybrid --- ### Tier 2: Battle-Tested Mainstream #### 5. **Next.js 15 (React)** | Aspect | Details | |--------|---------| | **What it is** | Most popular React meta-framework | | **Unique Feature** | App Router, Server Components, huge ecosystem | | **Learning Curve** | Medium-High (lots of concepts) | | **Ecosystem** | Largest - shadcn/ui, Radix, everything | | **Benchmark Score** | N/A (didn't query) | **Pros:** - Largest ecosystem and community - Most job opportunities - shadcn/ui provides beautiful, customizable components - Excellent documentation - Vercel hosting optimized **Cons:** - Can feel "generic" - many sites use it - Complex mental model (Server vs Client components) - Heavier than alternatives - Vercel-centric development --- #### 6. **Remix** | Aspect | Details | |--------|---------| | **What it is** | Full-stack React framework focused on web standards | | **Unique Feature** | Nested routing, progressive enhancement | | **Learning Curve** | Medium | | **Ecosystem** | Good - React ecosystem compatible | | **Benchmark Score** | 89.4 | **Pros:** - Web standards focused (works without JS) - Excellent data loading patterns - Great error handling - Form handling is first-class - Can deploy anywhere (not Vercel-locked) **Cons:** - Smaller community than Next.js - Less "magic" means more manual work - Merged with React Router (transition period) --- #### 7. **TanStack Start (React)** | Aspect | Details | |--------|---------| | **What it is** | New full-stack framework from TanStack team | | **Unique Feature** | Type-safe from database to UI | | **Learning Curve** | Medium | | **Ecosystem** | TanStack Query, Form, Router built-in | | **Benchmark Score** | 80.7 | **Pros:** - Built by TanStack (Query, Router, Form authors) - End-to-end type safety - Modern patterns throughout - Excellent data fetching built-in **Cons:** - Very new (beta/early stage) - Smaller community - Less documentation - Rapidly evolving API --- #### 8. **Nuxt 4 (Vue 3)** | Aspect | Details | |--------|---------| | **What it is** | Latest Vue meta-framework | | **Unique Feature** | Familiar from current system | | **Learning Curve** | Low (you know it) | | **Ecosystem** | Good - Nuxt UI, PrimeVue | **Pros:** - Familiar - no learning curve - Can reuse some current code/patterns - Strong conventions - Good TypeScript support now **Cons:** - User specifically wants to avoid "generic Vue look" - Similar limitations to current system - Less innovative than alternatives --- #### 9. **Angular 19** | Aspect | Details | |--------|---------| | **What it is** | Google's enterprise framework | | **Unique Feature** | Signals, standalone components, full framework | | **Learning Curve** | High | | **Ecosystem** | Enterprise-grade | | **Benchmark Score** | 90.3 | **Pros:** - Complete framework (no decisions to make) - Excellent for large applications - Strong typing throughout - Signals in Angular 19 are modern **Cons:** - Steeper learning curve - More verbose - "Enterprise" feel may not fit small org - Overkill for this scale --- ### Tier 3: Experimental/Niche #### 10. **Leptos (Rust)** | Aspect | Details | |--------|---------| | **What it is** | Full-stack Rust framework | | **Unique Feature** | WASM-based, extremely fast | | **Benchmark Score** | 89.7 | **Pros:** - Blazing fast (Rust + WASM) - Type safety at compile time - Innovative approach **Cons:** - Requires learning Rust - Small ecosystem - Harder to find developers - Overkill for this use case --- #### 11. **Hono + HTMX** | Aspect | Details | |--------|---------| | **What it is** | Lightweight backend + hypermedia frontend | | **Unique Feature** | Server-rendered, minimal JS | | **Benchmark Score** | 92.8 | **Pros:** - Extremely lightweight - Simple mental model - Works on edge (Cloudflare Workers) - Fast development **Cons:** - Less rich interactivity - Different paradigm (hypermedia) - Limited complex UI patterns - Manual work for dashboards --- ## UI COMPONENT LIBRARY OPTIONS ### For React-based Frameworks (Next.js, Remix, TanStack) | Library | Style | Customizable | Notes | |---------|-------|--------------|-------| | **shadcn/ui** | Modern, clean | Fully (copy/paste) | Most popular, highly customizable | | **Radix Themes** | Polished | Theme-based | Beautiful defaults, less work | | **Radix Primitives** | Unstyled | Fully | Build completely custom | | **Ark UI** | Unstyled | Fully | Works with multiple frameworks | | **Park UI** | Pre-styled Ark | Moderate | Ark + beautiful defaults | ### For Solid.js | Library | Style | Notes | |---------|-------|-------| | **Kobalte** | Unstyled | Radix-like primitives for Solid | | **Ark UI Solid** | Unstyled | Same Ark, Solid version | | **Solid UI** | Various | Community components | ### For Svelte | Library | Style | Notes | |---------|-------|-------| | **shadcn-svelte** | Modern | Port of shadcn for Svelte | | **Skeleton UI** | Tailwind | Full design system | | **Melt UI** | Unstyled | Primitives for Svelte | | **Bits UI** | Unstyled | Headless components | ### For Qwik | Library | Style | Notes | |---------|-------|-------| | **Qwik UI** | Official | Growing component library | | **Custom + Tailwind** | Any | Build from scratch | ### For Vue/Nuxt | Library | Style | Notes | |---------|-------|-------| | **shadcn-vue** | Modern | Port of shadcn for Vue | | **Radix Vue** | Unstyled | Radix primitives for Vue | | **Nuxt UI** | Tailwind | Official Nuxt components | | **PrimeVue** | Various | Comprehensive but generic | --- ## DATABASE OPTIONS - DETAILED COMPARISON ### Option 1: **Supabase** ⭐ RECOMMENDED | Aspect | Details | |--------|---------| | **Type** | PostgreSQL + Auth + Storage + Realtime | | **Hosting** | Managed cloud or self-hosted | | **Pricing** | Free tier, then $25/mo | **Pros:** - All-in-one: Database + Auth + File Storage + Realtime - PostgreSQL (industry standard, powerful) - Row-level security built-in - Excellent TypeScript support - Auto-generated APIs - Real-time subscriptions - Built-in auth (replaces Keycloak) - Dashboard for data management - Can self-host if needed **Cons:** - Vendor lock-in (mitigated by self-host option) - Learning curve for RLS policies - Free tier has limits - Less control than raw PostgreSQL **Best For:** Rapid development with full-stack features --- ### Option 2: **PostgreSQL + Prisma** | Aspect | Details | |--------|---------| | **Type** | Direct database + Type-safe ORM | | **Hosting** | Any PostgreSQL host (Neon, Railway, etc.) | | **Pricing** | Database hosting costs only | **Pros:** - Full control over database - Prisma schema is very readable - Excellent TypeScript types - Migrations handled automatically - Works with any PostgreSQL - Large community **Cons:** - Need separate auth solution - Need separate file storage - More setup work - Prisma can be slow for complex queries **Best For:** Maximum control and flexibility --- ### Option 3: **PostgreSQL + Drizzle ORM** | Aspect | Details | |--------|---------| | **Type** | Direct database + Lightweight ORM | | **Hosting** | Any PostgreSQL host | | **Pricing** | Database hosting costs only | **Pros:** - Closer to SQL (less abstraction) - Faster than Prisma - Smaller bundle size - TypeScript-first - Better for complex queries - Growing rapidly **Cons:** - Newer, smaller community - Less documentation - Need separate auth/storage - More manual migration work **Best For:** Performance-critical apps, SQL-comfortable teams --- ### Option 4: **PlanetScale + Drizzle** | Aspect | Details | |--------|---------| | **Type** | Serverless MySQL | | **Hosting** | Managed cloud only | | **Pricing** | Free tier, then usage-based | **Pros:** - Serverless scaling - Branching (like git for databases) - No connection limits - Fast globally **Cons:** - MySQL not PostgreSQL - No foreign keys (by design) - Vendor lock-in - Can get expensive at scale **Best For:** Serverless deployments, edge functions --- ### Option 5: **Keep NocoDB** | Aspect | Details | |--------|---------| | **Type** | Spreadsheet-like interface over database | | **Hosting** | Self-hosted or cloud | **Pros:** - Already configured - Non-technical users can edit data - Flexible schema changes - API already exists **Cons:** - Adds complexity layer - String booleans issue - Less type safety - Performance overhead - Limited query capabilities **Best For:** Non-technical admin users need direct access --- ## AUTHENTICATION OPTIONS - DETAILED COMPARISON ### Option 1: **Supabase Auth** ⭐ IF USING SUPABASE | Aspect | Details | |--------|---------| | **Type** | Built into Supabase | | **Providers** | Email, OAuth (Google, GitHub, etc.), Magic Link | **Pros:** - Integrated with Supabase (one platform) - Row-level security integration - Simple setup - Built-in user management - Social logins included - Magic link support **Cons:** - Tied to Supabase - Less customizable than Keycloak - No SAML/enterprise SSO on free tier --- ### Option 2: **Keep Keycloak** | Aspect | Details | |--------|---------| | **Type** | Self-hosted identity provider | | **Providers** | Everything (OIDC, SAML, social, etc.) | **Pros:** - Already configured and working - Enterprise-grade features - Full control - SAML support - Custom themes - User federation **Cons:** - Complex to maintain - Heavy resource usage - Overkill for small org - Requires Java expertise - Self-hosted burden --- ### Option 3: **Better Auth** ⭐ MODERN CHOICE | Aspect | Details | |--------|---------| | **Type** | Framework-agnostic TypeScript auth | | **Providers** | Email, OAuth, Magic Link, Passkeys | **Pros:** - Modern, TypeScript-first - Works with any framework - Plugin system for features - Session management built-in - Two-factor auth support - Lightweight **Cons:** - Newer (less battle-tested) - Self-implemented - Need own user storage --- ### Option 4: **Auth.js (NextAuth)** | Aspect | Details | |--------|---------| | **Type** | Framework-agnostic auth library | | **Providers** | 50+ OAuth providers | **Pros:** - Massive provider support - Well documented - Active development - Works with Qwik, SvelteKit, etc. **Cons:** - Complex configuration - Database adapter setup - v5 migration issues - Can be finicky --- ### Option 5: **Clerk** | Aspect | Details | |--------|---------| | **Type** | Auth-as-a-service | | **Providers** | Everything + beautiful UI | **Pros:** - Beautiful pre-built components - Zero config setup - Great DX - Organizations/teams built-in **Cons:** - Expensive at scale - Vendor lock-in - Less control - Monthly costs --- ### Option 6: **Lucia Auth** | Aspect | Details | |--------|---------| | **Type** | Low-level auth library | | **Note** | Being deprecated in favor of guides | **Pros:** - Full control - Lightweight - Educational **Cons:** - Being sunset - More DIY work --- ## CHOSEN STACK (FINAL) | Layer | Technology | Rationale | |-------|------------|-----------| | **Framework** | SvelteKit 2 | Best DX, simple syntax, excellent performance | | **UI Components** | shadcn-svelte + Bits UI | Beautiful, customizable, accessible | | **Styling** | Tailwind CSS 4 | Utility-first, works great with shadcn | | **Database** | Supabase (PostgreSQL) | All-in-one, managed, real-time capable | | **Auth** | Supabase Auth | Integrated with database, simple setup | | **File Storage** | Supabase Storage | Profile images, documents | | **Design** | Glass-morphism (evolved) | Modern, distinctive, refined | | **Language** | TypeScript | Type safety throughout | --- ## Phase 3: Architecture Design ### Project Structure ``` monacousa-portal-2026/ ├── src/ │ ├── lib/ │ │ ├── components/ # Reusable UI components │ │ │ ├── ui/ # shadcn-svelte base components │ │ │ ├── dashboard/ # Dashboard widgets │ │ │ ├── members/ # Member-related components │ │ │ ├── events/ # Event components │ │ │ └── layout/ # Layout components (sidebar, header) │ │ ├── server/ # Server-only utilities │ │ │ ├── supabase.ts # Supabase server client │ │ │ └── auth.ts # Auth helpers │ │ ├── stores/ # Svelte stores for state │ │ ├── utils/ # Shared utilities │ │ │ ├── types.ts # TypeScript types │ │ │ ├── constants.ts # App constants │ │ │ └── helpers.ts # Helper functions │ │ └── supabase.ts # Supabase client (browser) │ │ │ ├── routes/ │ │ ├── +layout.svelte # Root layout │ │ ├── +layout.server.ts # Root server load (auth) │ │ ├── +page.svelte # Landing page │ │ │ │ │ ├── (auth)/ # Auth group (guest only) │ │ │ ├── login/ │ │ │ ├── signup/ │ │ │ ├── forgot-password/ │ │ │ └── callback/ # OAuth callback │ │ │ │ │ ├── (app)/ # Protected app group │ │ │ ├── +layout.svelte # App layout with sidebar │ │ │ ├── +layout.server.ts # Auth guard │ │ │ │ │ │ │ ├── dashboard/ # User dashboard │ │ │ ├── profile/ # User profile │ │ │ ├── events/ # Events calendar/list │ │ │ ├── payments/ # Dues/payments view │ │ │ │ │ │ │ ├── board/ # Board-only routes │ │ │ │ ├── +layout.server.ts # Board guard │ │ │ │ ├── dashboard/ │ │ │ │ ├── members/ │ │ │ │ ├── events/ # Event management │ │ │ │ └── meetings/ │ │ │ │ │ │ │ └── admin/ # Admin-only routes │ │ │ ├── +layout.server.ts # Admin guard │ │ │ ├── dashboard/ │ │ │ ├── members/ │ │ │ ├── users/ │ │ │ ├── events/ │ │ │ └── settings/ │ │ │ │ │ └── api/ # API routes (if needed) │ │ │ ├── hooks.server.ts # Server hooks (Supabase SSR) │ └── app.d.ts # TypeScript declarations │ ├── static/ # Static assets ├── supabase/ # Supabase local dev │ └── migrations/ # Database migrations ├── tests/ # Test files ├── svelte.config.js ├── tailwind.config.ts ├── vite.config.ts └── package.json ``` --- ### Database Schema (Supabase/PostgreSQL) ```sql -- USERS (managed by Supabase Auth) -- auth.users table is automatic -- MEMBERS (extends auth.users) CREATE TABLE public.members ( id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, member_id TEXT UNIQUE NOT NULL, -- MUSA-2026-0001 format first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, phone TEXT, date_of_birth DATE, address TEXT, nationality TEXT[], -- Array of country codes ['FR', 'US'] -- Membership role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('member', 'board', 'admin')), membership_status TEXT NOT NULL DEFAULT 'pending' CHECK (membership_status IN ('active', 'inactive', 'pending', 'expired')), member_since DATE DEFAULT CURRENT_DATE, -- Profile avatar_url TEXT, bio TEXT, -- Timestamps created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- DUES/PAYMENTS (tracks payment history) CREATE TABLE public.dues_payments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), member_id UUID NOT NULL REFERENCES public.members(id) ON DELETE CASCADE, amount DECIMAL(10,2) NOT NULL, currency TEXT DEFAULT 'EUR', payment_date DATE NOT NULL, due_date DATE NOT NULL, -- When this payment period ends payment_method TEXT, -- 'bank_transfer', 'cash', etc. reference TEXT, -- Transaction reference notes TEXT, recorded_by UUID REFERENCES public.members(id), -- Who recorded this payment created_at TIMESTAMPTZ DEFAULT NOW() ); -- EVENTS CREATE TABLE public.events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL, description TEXT, event_type TEXT NOT NULL CHECK (event_type IN ('social', 'meeting', 'fundraiser', 'workshop', 'other')), start_datetime TIMESTAMPTZ NOT NULL, end_datetime TIMESTAMPTZ NOT NULL, location TEXT, -- Capacity & Pricing max_attendees INTEGER, max_guests_per_member INTEGER DEFAULT 1, member_price DECIMAL(10,2) DEFAULT 0, non_member_price DECIMAL(10,2) DEFAULT 0, -- Visibility visibility TEXT NOT NULL DEFAULT 'members' CHECK (visibility IN ('public', 'members', 'board', 'admin')), status TEXT NOT NULL DEFAULT 'published' CHECK (status IN ('draft', 'published', 'cancelled', 'completed')), -- Metadata created_by UUID NOT NULL REFERENCES public.members(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- EVENT RSVPs CREATE TABLE public.event_rsvps ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_id UUID NOT NULL REFERENCES public.events(id) ON DELETE CASCADE, member_id UUID NOT NULL REFERENCES public.members(id) ON DELETE CASCADE, status TEXT NOT NULL DEFAULT 'confirmed' CHECK (status IN ('confirmed', 'declined', 'waitlist', 'cancelled')), guest_count INTEGER DEFAULT 0, guest_names TEXT[], payment_status TEXT DEFAULT 'not_required' CHECK (payment_status IN ('not_required', 'pending', 'paid')), attended BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(event_id, member_id) ); -- DOCUMENTS (meeting minutes, governance, etc.) CREATE TABLE public.documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL, description TEXT, category TEXT NOT NULL CHECK (category IN ('meeting_minutes', 'governance', 'financial', 'other')), file_path TEXT NOT NULL, -- Supabase Storage path file_name TEXT NOT NULL, file_size INTEGER, mime_type TEXT, visibility TEXT NOT NULL DEFAULT 'board' CHECK (visibility IN ('members', 'board', 'admin')), uploaded_by UUID NOT NULL REFERENCES public.members(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- EMAIL NOTIFICATIONS LOG CREATE TABLE public.email_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), recipient_id UUID REFERENCES public.members(id), recipient_email TEXT NOT NULL, email_type TEXT NOT NULL, -- 'dues_reminder', 'event_invite', etc. subject TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'sent' CHECK (status IN ('sent', 'failed', 'bounced')), sent_at TIMESTAMPTZ DEFAULT NOW(), error_message TEXT ); -- COMPUTED VIEW: Member with dues status CREATE VIEW public.members_with_dues AS SELECT m.*, dp.payment_date as last_payment_date, dp.due_date as current_due_date, CASE WHEN dp.due_date IS NULL THEN 'never_paid' WHEN dp.due_date < CURRENT_DATE THEN 'overdue' WHEN dp.due_date < CURRENT_DATE + INTERVAL '30 days' THEN 'due_soon' ELSE 'current' END as dues_status, CASE WHEN dp.due_date < CURRENT_DATE THEN CURRENT_DATE - dp.due_date ELSE NULL END as days_overdue FROM public.members m LEFT JOIN LATERAL ( SELECT payment_date, due_date FROM public.dues_payments WHERE member_id = m.id ORDER BY due_date DESC LIMIT 1 ) dp ON true; -- ROW LEVEL SECURITY ALTER TABLE public.members ENABLE ROW LEVEL SECURITY; ALTER TABLE public.dues_payments ENABLE ROW LEVEL SECURITY; ALTER TABLE public.events ENABLE ROW LEVEL SECURITY; ALTER TABLE public.event_rsvps ENABLE ROW LEVEL SECURITY; -- Members: Users can read all, update own, admins can do anything CREATE POLICY "Members are viewable by authenticated users" ON public.members FOR SELECT TO authenticated USING (true); CREATE POLICY "Users can update own profile" ON public.members FOR UPDATE TO authenticated USING (auth.uid() = id); CREATE POLICY "Admins can insert members" ON public.members FOR INSERT TO authenticated WITH CHECK ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') OR auth.uid() = id -- Self-registration ); -- Events: Based on visibility CREATE POLICY "Events viewable based on visibility" ON public.events FOR SELECT TO authenticated USING ( visibility = 'members' OR visibility = 'public' OR (visibility = 'board' AND EXISTS ( SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin') )) OR (visibility = 'admin' AND EXISTS ( SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin' )) ); -- Board/Admin can manage events CREATE POLICY "Board can manage events" ON public.events FOR ALL TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) ); ``` --- ### Authentication Flow ``` 1. SIGNUP FLOW ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ /signup │────▶│ Supabase Auth│────▶│ Email Verify│ │ Form │ │ signUp() │ │ Link Sent │ └─────────────┘ └──────────────┘ └─────────────┘ │ ▼ ┌──────────────┐ │ Create Member│ │ Record (RLS) │ └──────────────┘ 2. LOGIN FLOW ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ /login │────▶│ Supabase Auth│────▶│ Set Session │ │ Form │ │ signIn() │ │ Cookie │ └─────────────┘ └──────────────┘ └─────────────┘ │ ▼ ┌──────────────┐ │ Redirect to │ │ Dashboard │ └──────────────┘ 3. PROTECTED ROUTES ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Request │────▶│ hooks.server │────▶│ Check Role │ │ /admin/* │ │ getSession() │ │ in members │ └─────────────┘ └──────────────┘ └─────────────┘ │ │ ▼ ▼ ┌──────────────┐ ┌─────────────┐ │ Valid? │ │ Redirect or │ │ Yes → Render │ │ 403 Error │ └──────────────┘ └─────────────┘ ``` --- ### UI Component Library Using **shadcn-svelte** with custom glass-morphism theme: ```typescript // tailwind.config.ts - Glass theme extensions export default { theme: { extend: { colors: { monaco: { 50: '#fef2f2', 100: '#fee2e2', 500: '#ef4444', 600: '#dc2626', // Primary 700: '#b91c1c', 900: '#7f1d1d', } }, backdropBlur: { xs: '2px', }, boxShadow: { 'glass': '0 8px 32px rgba(0, 0, 0, 0.1)', 'glass-lg': '0 25px 50px rgba(0, 0, 0, 0.15)', } } } } ``` **Custom Glass Components:** - `GlassCard` - Frosted glass container - `GlassSidebar` - Navigation sidebar - `GlassButton` - Glass-effect buttons - `GlassInput` - Form inputs with glass styling - `StatCard` - Dashboard stat display - `EventCard` - Event display card - `MemberCard` - Member profile card - `DuesStatusBadge` - Dues status indicator --- ### Key Features Implementation #### 1. Member Management - View all members (admin/board) - Edit member details - Upload profile photos (Supabase Storage) - Track membership status - Filter by status, nationality, dues #### 2. Dues Tracking - Payment history table - Auto-calculate due dates (1 year from payment) - Visual status indicators - Overdue notifications - Manual payment recording #### 3. Event System - Calendar view (FullCalendar or custom) - List view with filters - RSVP with guest management - Attendance tracking - Event creation (board/admin) #### 4. Three Dashboards | Dashboard | Features | |-----------|----------| | **Member** | Profile, upcoming events, dues status, quick actions | | **Board** | Member stats, pending applications, dues overview, event management | | **Admin** | System stats, user management, all member data, settings | #### 5. Email Notifications - Dues reminder emails (30 days before, on due date, overdue) - Event invitation/updates - Welcome email on signup - Password reset emails (Supabase built-in) #### 6. Document Storage - Upload meeting minutes, governance docs - Organize by category - Visibility controls (members/board/admin) - Download/preview functionality --- ## Phase 4: Implementation Roadmap ### Stage 1: Foundation (Week 1-2) 1. Initialize SvelteKit project with TypeScript 2. Set up Tailwind CSS 4 + shadcn-svelte 3. Configure Supabase project 4. Create database schema + migrations 5. Implement Supabase SSR hooks 6. Build base layout components ### Stage 2: Authentication (Week 2-3) 1. Login/Signup pages 2. Email verification flow 3. Password reset 4. Protected route guards 5. Role-based access control ### Stage 3: Core Features (Week 3-5) 1. Member dashboard 2. Profile management 3. Member directory (board/admin) 4. Dues tracking system 5. Payment recording ### Stage 4: Events (Week 5-6) 1. Event listing/calendar 2. Event detail view 3. RSVP system 4. Event creation (board) 5. Attendance tracking ### Stage 5: Admin Features (Week 6-7) 1. Admin dashboard 2. User management 3. System settings 4. Data export ### Stage 6: Polish (Week 7-8) 1. Glass-morphism styling refinement 2. Responsive design 3. Performance optimization 4. Testing 5. Documentation --- ## Verification Plan ### Development Testing ```bash # Start Supabase local npx supabase start # Run dev server npm run dev # Type checking npm run check ``` ### Manual Testing Checklist - [ ] User can sign up and receive verification email - [ ] User can log in and see dashboard - [ ] Member can view/edit profile - [ ] Member can view events and RSVP - [ ] Board member can access board dashboard - [ ] Board member can create/manage events - [ ] Board member can view member directory - [ ] Board member can record dues payments - [ ] Admin can access all features - [ ] Admin can manage user roles - [ ] Role-based routing works correctly - [ ] Responsive on mobile/tablet/desktop ### Browser Testing - Chrome, Firefox, Safari, Edge - iOS Safari, Android Chrome --- ## Data Entry Strategy Since members will be entered manually (no automated migration): ### Admin Setup 1. Create first admin account via Supabase dashboard 2. Manually set `role = 'admin'` in members table 3. Admin can then add other members through the portal ### Member Entry Options 1. **Admin adds members** - Admin creates accounts for existing members 2. **Self-registration** - Members sign up themselves 3. **Invite system** - Admin sends email invites with signup links ### Initial Launch Checklist - [ ] Admin account created and verified - [ ] Board member accounts created - [ ] Test member account for verification - [ ] Email templates configured (Supabase) --- ## Files to Create | Path | Purpose | |------|---------| | `monacousa-portal-2026/` | New project root | | `src/hooks.server.ts` | Supabase SSR setup | | `src/lib/supabase.ts` | Client initialization | | `src/lib/server/supabase.ts` | Server client | | `src/routes/+layout.svelte` | Root layout | | `src/routes/(auth)/login/+page.svelte` | Login page | | `src/routes/(auth)/signup/+page.svelte` | Signup page | | `src/routes/(app)/+layout.svelte` | App layout | | `src/routes/(app)/dashboard/+page.svelte` | Member dashboard | | `supabase/migrations/001_schema.sql` | Database schema |