# 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
<!-- routes/(app)/dashboard/+page.svelte -->
<script>
  export let data; // { member, events, stats }
  const { member } = data;
  const isBoard = member.role === 'board' || member.role === 'admin';
  const isAdmin = member.role === 'admin';
</script>

<!-- Everyone sees these -->
<WelcomeCard {member} />
<DuesStatusCard {member} />
<UpcomingEventsCard events={data.upcomingEvents} />

<!-- Board and Admin see these -->
{#if isBoard}
  <Separator title="Board Tools" />
  <MemberStatsCard stats={data.memberStats} />
  <PendingMembersCard members={data.pendingMembers} />
  <DuesOverviewCard overview={data.duesOverview} />
{/if}

<!-- Admin only sees these -->
{#if isAdmin}
  <Separator title="Admin" />
  <SystemHealthCard health={data.systemHealth} />
  <RecentActivityCard activity={data.recentActivity} />
  <QuickActionsCard />
{/if}
```

### 4.7 Member Dashboard Section

**Components:**
1. **Welcome Card** - Greeting with name, membership status badge
2. **Dues Status Card** - Current status, next due date, quick pay info
3. **Upcoming Events Card** - Next 3-5 events with RSVP status
4. **Profile Quick View** - Photo, basic info, edit link

**Data Loaded:**
```typescript
// routes/(app)/dashboard/+page.server.ts
export const load = async ({ locals }) => {
  const { member } = await locals.safeGetSession();

  const upcomingEvents = await getUpcomingEventsForMember(member.id, 5);

  return {
    member,
    upcomingEvents
  };
};
```

### 4.8 Board Dashboard Section

**Additional Components (visible to board/admin):**
1. **Member Stats Card** - Total, active, pending, inactive counts
2. **Pending Members Card** - New signups awaiting approval/payment
3. **Dues Overview Card** - Current, due soon, overdue breakdown
4. **Recent RSVPs Card** - Latest event RSVPs

**Board Stats:**
```typescript
interface BoardStats {
  totalMembers: number;
  activeMembers: number;
  pendingMembers: number;
  inactiveMembers: number;
  duesSoon: number;      // Due in next 30 days
  duesOverdue: number;   // Past due date
  upcomingEvents: number;
  pendingRsvps: number;
}
```

### 4.9 Admin Dashboard Section

**Additional Components (admin only):**
1. **System Health Card** - Supabase status, email status
2. **Recent Activity Card** - Latest logins, signups, payments
3. **Quick Actions Card** - Add member, create event, send broadcast
4. **Alerts Card** - Issues requiring attention

**Admin Stats:**
```typescript
interface AdminStats extends BoardStats {
  totalUsers: number;          // Auth users
  recentLogins: number;        // Last 24 hours
  failedLogins: number;        // Last 24 hours
  emailsSent: number;          // This month
  storageUsed: number;         // MB
}
```

### 4.10 Route Protection

**Layout-level guards using SvelteKit:**

```typescript
// routes/(app)/+layout.server.ts
import { redirect } from '@sveltejs/kit';

export const load = async ({ locals }) => {
  const { session, member } = await locals.safeGetSession();

  if (!session) {
    throw redirect(303, '/login');
  }

  return { member };
};

// routes/(app)/board/+layout.server.ts
export const load = async ({ locals, parent }) => {
  const { member } = await parent();

  if (member.role !== 'board' && member.role !== 'admin') {
    throw redirect(303, '/dashboard');
  }

  return {};
};

// routes/(app)/admin/+layout.server.ts
export const load = async ({ locals, parent }) => {
  const { member } = await parent();

  if (member.role !== 'admin') {
    throw redirect(303, '/dashboard');
  }

  return {};
};
```

### 4.11 Responsive Breakpoints

| Breakpoint | Width | Layout |
|------------|-------|--------|
| Mobile | < 640px | Bottom nav, stacked cards |
| Tablet | 640-1024px | Collapsed sidebar rail, 2-column |
| Desktop | > 1024px | Full sidebar, 3-column grid |

### 4.12 Dashboard Glass-Morphism Design

**Glass Card Base Style:**
```css
.glass-card {
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

.glass-card-dark {
  background: rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.1);
}
```

**Monaco Red Accent:**
```css
:root {
  --monaco-red: #dc2626;
  --monaco-red-light: #fee2e2;
  --monaco-red-dark: #991b1b;
}
```

---

## 5. DOCUMENT STORAGE (Detailed)

### 5.1 Document Categories (Admin-Configurable)

```sql
CREATE TABLE public.document_categories (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL UNIQUE,
  display_name TEXT NOT NULL,
  description TEXT,
  icon TEXT,                              -- Lucide icon name
  sort_order INTEGER DEFAULT 0,
  is_active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Default categories
INSERT INTO document_categories (name, display_name, icon) VALUES
  ('meeting_minutes', 'Meeting Minutes', 'file-text'),
  ('governance', 'Governance & Bylaws', 'scale'),
  ('legal', 'Legal Documents', 'briefcase'),
  ('financial', 'Financial Reports', 'dollar-sign'),
  ('member_resources', 'Member Resources', 'book-open'),
  ('forms', 'Forms & Templates', 'clipboard'),
  ('other', 'Other Documents', 'file');
```

### 5.2 Upload Permissions

**Who can upload:**
- Board members
- Administrators

**Members cannot upload** - they can only view documents shared with them.

### 5.3 Document Visibility (Per-Document)

**Visibility Options:**
| Level | Who Can View |
|-------|--------------|
| `public` | Anyone (no login required) |
| `members` | All logged-in members |
| `board` | Board + Admin only |
| `admin` | Admin only |

**Custom permissions** can also specify specific member IDs for restricted access.

### 5.4 Document Schema

```sql
CREATE TABLE public.documents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

  -- Basic Info
  title TEXT NOT NULL,
  description TEXT,
  category_id UUID REFERENCES public.document_categories(id),

  -- File Info (Supabase Storage)
  file_path TEXT NOT NULL,               -- Storage path
  file_name TEXT NOT NULL,               -- Original filename
  file_size INTEGER NOT NULL,            -- Bytes
  mime_type TEXT NOT NULL,               -- 'application/pdf', etc.

  -- Visibility
  visibility TEXT NOT NULL DEFAULT 'members'
    CHECK (visibility IN ('public', 'members', 'board', 'admin')),

  -- Optional: Specific member access (for restricted docs)
  allowed_member_ids UUID[],             -- If set, only these members can view

  -- Version tracking
  version INTEGER DEFAULT 1,
  replaces_document_id UUID REFERENCES public.documents(id),

  -- Metadata
  uploaded_by UUID NOT NULL REFERENCES public.members(id),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Document access log (for audit)
CREATE TABLE public.document_access_log (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  document_id UUID NOT NULL REFERENCES public.documents(id) ON DELETE CASCADE,
  accessed_by UUID REFERENCES public.members(id),  -- null if public access
  access_type TEXT NOT NULL CHECK (access_type IN ('view', 'download')),
  ip_address TEXT,
  accessed_at TIMESTAMPTZ DEFAULT NOW()
);
```

### 5.5 File Storage (Supabase Storage)

**Bucket Configuration:**
```typescript
// Storage bucket: 'documents'
// Path structure: documents/{category}/{year}/{filename}

// Example paths:
// documents/meeting_minutes/2026/board-meeting-2026-01-15.pdf
// documents/governance/bylaws-v2.pdf
// documents/financial/2025/annual-report-2025.pdf
```

**Upload Limits:**
- Max file size: 50MB
- Allowed types: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT, JPG, PNG

### 5.6 Document UI Features

**Document Library View:**
- Filter by category
- Filter by visibility level
- Search by title/description
- Sort by date, name, category
- Grid or list view toggle

**Document Card:**
```
┌────────────────────────────────────────┐
│ 📄 [Category Icon]                      │
│                                         │
│ Board Meeting Minutes - January 2026   │
│ Meeting minutes from the monthly...    │
│                                         │
│ 📅 Jan 15, 2026  |  📎 PDF  |  1.2 MB  │
│                                         │
│ [View] [Download]  👁️ Members          │
└────────────────────────────────────────┘
```

**Upload Form (Board/Admin):**
- Title (required)
- Description (optional)
- Category (required, dropdown)
- Visibility (required)
- Custom access (optional, member multi-select)
- File upload (drag & drop)

### 5.7 Document Permissions (RLS)

```sql
-- RLS Policies for documents
ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;

-- Public documents viewable by anyone
CREATE POLICY "Public documents are viewable"
  ON public.documents FOR SELECT
  USING (visibility = 'public');

-- Member documents viewable by authenticated users
CREATE POLICY "Member documents viewable by members"
  ON public.documents 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'
    ))
    OR (allowed_member_ids IS NOT NULL AND auth.uid() = ANY(allowed_member_ids))
  );

-- Board/Admin can manage documents
CREATE POLICY "Board can upload documents"
  ON public.documents FOR INSERT
  TO authenticated
  WITH CHECK (
    EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin'))
  );

CREATE POLICY "Uploader or admin can update documents"
  ON public.documents FOR UPDATE
  TO authenticated
  USING (
    uploaded_by = auth.uid()
    OR EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin')
  );

CREATE POLICY "Admin can delete documents"
  ON public.documents FOR DELETE
  TO authenticated
  USING (
    EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin')
  );
```

### 5.8 Version History

**Document versioning:**
- When replacing a document, create new record with `replaces_document_id`
- Previous versions remain accessible (archived)
- View version history for any document

```sql
-- Get version history for a document
SELECT d.*, m.first_name, m.last_name
FROM public.documents d
JOIN public.members m ON d.uploaded_by = m.id
WHERE d.id = :document_id
   OR d.replaces_document_id = :document_id
   OR d.id IN (
     SELECT replaces_document_id FROM public.documents
     WHERE id = :document_id
   )
ORDER BY d.version DESC;
```

### 5.9 Meeting Minutes Special Handling

**For meeting minutes category:**
- Date field (meeting date)
- Attendees list (optional)
- Agenda reference (optional)
- Quick template for consistency

```sql
-- Optional meeting minutes metadata
ALTER TABLE public.documents ADD COLUMN meeting_date DATE;
ALTER TABLE public.documents ADD COLUMN meeting_attendees UUID[];
```

---

## 6. ADMIN SETTINGS SYSTEM (Detailed)

### 6.1 Settings Architecture Overview

**Centralized configuration** for all customizable aspects of the portal, accessible only to Admins via `/admin/settings`.

**Settings Categories:**
1. **Organization** - Association branding and info
2. **Membership** - Statuses, types, and pricing
3. **Dues** - Payment settings and reminders
4. **Events** - Event types and defaults
5. **Documents** - Categories and storage
6. **Directory** - Visibility controls
7. **Email** - SMTP and template settings
8. **System** - Technical settings

### 6.2 Settings Storage (Unified Table)

```sql
-- Flexible key-value settings with JSON support
CREATE TABLE public.app_settings (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  category TEXT NOT NULL,                    -- 'organization', 'dues', 'email', etc.
  setting_key TEXT NOT NULL,                 -- 'payment_iban', 'reminder_days', etc.
  setting_value JSONB NOT NULL,              -- Supports strings, numbers, arrays, objects
  setting_type TEXT NOT NULL DEFAULT 'text'  -- 'text', 'number', 'boolean', 'json', 'array'
    CHECK (setting_type IN ('text', 'number', 'boolean', 'json', 'array')),
  display_name TEXT NOT NULL,                -- Human-readable label
  description TEXT,                          -- Help text for admins
  is_public BOOLEAN DEFAULT FALSE,           -- If true, accessible without auth
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  updated_by UUID REFERENCES public.members(id),

  UNIQUE(category, setting_key)
);

-- Audit log for settings changes
CREATE TABLE public.settings_audit_log (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  setting_id UUID NOT NULL REFERENCES public.app_settings(id),
  old_value JSONB,
  new_value JSONB NOT NULL,
  changed_by UUID NOT NULL REFERENCES public.members(id),
  changed_at TIMESTAMPTZ DEFAULT NOW(),
  change_reason TEXT
);

-- RLS: Only admins can read/write settings
ALTER TABLE public.app_settings ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Only admins can manage settings"
  ON public.app_settings FOR ALL
  TO authenticated
  USING (
    EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin')
    OR is_public = TRUE
  );
```

### 6.3 Default Settings (Seeded on First Run)

```sql
-- Organization Settings
INSERT INTO app_settings (category, setting_key, setting_value, setting_type, display_name, description, is_public) VALUES
  ('organization', 'association_name', '"Monaco USA"', 'text', 'Association Name', 'Official name of the association', true),
  ('organization', 'tagline', '"Americans in Monaco"', 'text', 'Tagline', 'Association tagline shown on login', true),
  ('organization', 'contact_email', '"contact@monacousa.org"', 'text', 'Contact Email', 'Public contact email address', true),
  ('organization', 'address', '"Monaco"', 'text', 'Address', 'Association physical address', true),
  ('organization', 'logo_url', '"/logo.png"', 'text', 'Logo URL', 'Path to association logo', true),
  ('organization', 'primary_color', '"#dc2626"', 'text', 'Primary Color', 'Brand primary color (hex)', true),

-- Dues Settings
  ('dues', 'payment_iban', '"MC58 1756 9000 0104 0050 1001 860"', 'text', 'Payment IBAN', 'Bank IBAN for dues payment', false),
  ('dues', 'payment_account_holder', '"ASSOCIATION MONACO USA"', 'text', 'Account Holder', 'Bank account holder name', false),
  ('dues', 'payment_bank_name', '"Credit Foncier de Monaco"', 'text', 'Bank Name', 'Name of the bank', false),
  ('dues', 'payment_instructions', '"Please include your Member ID (MUSA-XXXX) in the reference"', 'text', 'Payment Instructions', 'Instructions shown to members', false),
  ('dues', 'reminder_days_before', '[30, 7, 1]', 'array', 'Reminder Days', 'Days before due date to send reminders', false),
  ('dues', 'grace_period_days', '30', 'number', 'Grace Period', 'Days after due date before auto-inactive', false),
  ('dues', 'overdue_reminder_interval', '14', 'number', 'Overdue Reminder Interval', 'Days between overdue reminder emails', false),
  ('dues', 'auto_inactive_enabled', 'true', 'boolean', 'Auto Inactive', 'Automatically set members inactive after grace period', false),

-- Event Settings
  ('events', 'default_max_guests', '2', 'number', 'Default Max Guests', 'Default maximum guests per RSVP', false),
  ('events', 'reminder_hours_before', '[24, 1]', 'array', 'Event Reminder Hours', 'Hours before event to send reminders', false),
  ('events', 'allow_public_rsvp', 'true', 'boolean', 'Allow Public RSVP', 'Allow non-members to RSVP to public events', false),
  ('events', 'auto_close_rsvp_hours', '0', 'number', 'Auto Close RSVP', 'Hours before event to close RSVP (0 = never)', false),

-- Directory Settings
  ('directory', 'member_visible_fields', '["first_name", "last_name", "avatar_url", "nationality", "member_since"]', 'array', 'Member Visible Fields', 'Fields visible to regular members', false),
  ('directory', 'board_visible_fields', '["first_name", "last_name", "avatar_url", "nationality", "email", "phone", "address", "date_of_birth", "member_since", "membership_status"]', 'array', 'Board Visible Fields', 'Fields visible to board members', false),
  ('directory', 'show_membership_status', 'false', 'boolean', 'Show Status to Members', 'Show membership status in directory for regular members', false),

-- System Settings
  ('system', 'maintenance_mode', 'false', 'boolean', 'Maintenance Mode', 'Put the portal in maintenance mode', false),
  ('system', 'maintenance_message', '"The portal is currently undergoing maintenance. Please check back soon."', 'text', 'Maintenance Message', 'Message shown during maintenance', false),
  ('system', 'session_timeout_hours', '168', 'number', 'Session Timeout', 'Hours until session expires (default: 7 days)', false),
  ('system', 'max_upload_size_mb', '50', 'number', 'Max Upload Size', 'Maximum file upload size in MB', false),
  ('system', 'allowed_file_types', '["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "jpg", "jpeg", "png", "webp"]', 'array', 'Allowed File Types', 'Allowed file extensions for uploads', false);
```

### 6.4 Settings UI Layout

**Navigation Tabs:**
```
┌──────────────────────────────────────────────────────────────────┐
│  ⚙️ Settings                                                      │
├──────────────────────────────────────────────────────────────────┤
│  [Organization] [Membership] [Dues] [Events] [Documents]         │
│  [Directory] [Email] [System]                                    │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────────────────────────────────────────────┐ │
│  │  Organization Settings                                       │ │
│  │  ─────────────────────────────────────────────────────────  │ │
│  │                                                              │ │
│  │  Association Name                                            │ │
│  │  ┌──────────────────────────────────────────────────────┐   │ │
│  │  │ Monaco USA                                            │   │ │
│  │  └──────────────────────────────────────────────────────┘   │ │
│  │  Official name of the association                            │ │
│  │                                                              │ │
│  │  Tagline                                                     │ │
│  │  ┌──────────────────────────────────────────────────────┐   │ │
│  │  │ Americans in Monaco                                   │   │ │
│  │  └──────────────────────────────────────────────────────┘   │ │
│  │  Association tagline shown on login                          │ │
│  │                                                              │ │
│  │  Primary Color                                               │ │
│  │  ┌────────┐ ┌──────────────────────────────────────────┐    │ │
│  │  │ 🎨     │ │ #dc2626                                   │    │ │
│  │  └────────┘ └──────────────────────────────────────────┘    │ │
│  │                                                              │ │
│  │           [Save Changes]                                     │ │
│  └─────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
```

### 6.5 Membership Settings Tab

**Manages configurable membership statuses and types:**

```
┌──────────────────────────────────────────────────────────────────┐
│  Membership Settings                                             │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  MEMBERSHIP STATUSES                                             │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  ┌───────────┬─────────────┬──────────┬────────────┬──────────┐ │
│  │ Name      │ Display     │ Color    │ Is Default │ Actions  │ │
│  ├───────────┼─────────────┼──────────┼────────────┼──────────┤ │
│  │ pending   │ Pending     │ 🟡 Yellow│ ✓          │ ✏️ 🗑️     │ │
│  │ active    │ Active      │ 🟢 Green │            │ ✏️ 🗑️     │ │
│  │ inactive  │ Inactive    │ ⚪ Gray  │            │ ✏️ 🗑️     │ │
│  │ expired   │ Expired     │ 🔴 Red   │            │ ✏️ 🗑️     │ │
│  └───────────┴─────────────┴──────────┴────────────┴──────────┘ │
│                                                                  │
│  [+ Add Status]                                                  │
│                                                                  │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  MEMBERSHIP TYPES                                                │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  ┌───────────┬───────────────┬──────────┬────────────┬────────┐ │
│  │ Name      │ Display       │ Annual € │ Is Default │Actions │ │
│  ├───────────┼───────────────┼──────────┼────────────┼────────┤ │
│  │ regular   │ Regular       │ €50.00   │ ✓          │ ✏️ 🗑️   │ │
│  │ student   │ Student       │ €25.00   │            │ ✏️ 🗑️   │ │
│  │ senior    │ Senior (65+)  │ €35.00   │            │ ✏️ 🗑️   │ │
│  │ family    │ Family        │ €75.00   │            │ ✏️ 🗑️   │ │
│  │ honorary  │ Honorary      │ €0.00    │            │ ✏️ 🗑️   │ │
│  └───────────┴───────────────┴──────────┴────────────┴────────┘ │
│                                                                  │
│  [+ Add Membership Type]                                         │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### 6.6 Event Types Settings

**Admin can manage event types with colors and icons:**

```
┌──────────────────────────────────────────────────────────────────┐
│  Event Types                                                     │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┬───────────────┬────────────┬────────┬────────┐ │
│  │ Name        │ Display       │ Color      │ Icon   │Actions │ │
│  ├─────────────┼───────────────┼────────────┼────────┼────────┤ │
│  │ social      │ Social Event  │ 🟢 #10b981 │ 🎉     │ ✏️ 🗑️   │ │
│  │ meeting     │ Meeting       │ 🔵 #6366f1 │ 👥     │ ✏️ 🗑️   │ │
│  │ fundraiser  │ Fundraiser    │ 🟠 #f59e0b │ 💝     │ ✏️ 🗑️   │ │
│  │ workshop    │ Workshop      │ 🟣 #8b5cf6 │ 🎓     │ ✏️ 🗑️   │ │
│  │ gala        │ Gala/Formal   │ 🌸 #ec4899 │ ✨     │ ✏️ 🗑️   │ │
│  │ other       │ Other         │ ⚫ #6b7280 │ 📅     │ ✏️ 🗑️   │ │
│  └─────────────┴───────────────┴────────────┴────────┴────────┘ │
│                                                                  │
│  [+ Add Event Type]                                              │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### 6.7 Document Categories Settings

```
┌──────────────────────────────────────────────────────────────────┐
│  Document Categories                                             │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────┬─────────────────────┬────────┬────────────┐ │
│  │ Name            │ Display             │ Icon   │ Actions    │ │
│  ├─────────────────┼─────────────────────┼────────┼────────────┤ │
│  │ meeting_minutes │ Meeting Minutes     │ 📄     │ ✏️ 🗑️       │ │
│  │ governance      │ Governance & Bylaws │ ⚖️     │ ✏️ 🗑️       │ │
│  │ legal           │ Legal Documents     │ 💼     │ ✏️ 🗑️       │ │
│  │ financial       │ Financial Reports   │ 💰     │ ✏️ 🗑️       │ │
│  │ member_resources│ Member Resources    │ 📚     │ ✏️ 🗑️       │ │
│  │ forms           │ Forms & Templates   │ 📋     │ ✏️ 🗑️       │ │
│  │ other           │ Other Documents     │ 📁     │ ✏️ 🗑️       │ │
│  └─────────────────┴─────────────────────┴────────┴────────────┘ │
│                                                                  │
│  [+ Add Category]                                                │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### 6.8 Directory Visibility Settings

**Admin controls what fields are visible to different roles:**

```
┌──────────────────────────────────────────────────────────────────┐
│  Directory Visibility                                            │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Configure which member fields are visible in the directory.     │
│  Admin always sees all fields.                                   │
│                                                                  │
│  ┌─────────────────┬──────────────────┬──────────────────┐      │
│  │ Field           │ Visible to       │ Visible to       │      │
│  │                 │ Members          │ Board            │      │
│  ├─────────────────┼──────────────────┼──────────────────┤      │
│  │ First Name      │ ☑️ Always shown   │ ☑️ Always shown   │      │
│  │ Last Name       │ ☑️ Always shown   │ ☑️ Always shown   │      │
│  │ Profile Photo   │ ☑️                │ ☑️                │      │
│  │ Nationality     │ ☑️                │ ☑️                │      │
│  │ Email           │ ☐                │ ☑️                │      │
│  │ Phone           │ ☐                │ ☑️                │      │
│  │ Address         │ ☐                │ ☑️                │      │
│  │ Date of Birth   │ ☐                │ ☑️                │      │
│  │ Member Since    │ ☑️                │ ☑️                │      │
│  │ Status          │ ☐                │ ☑️                │      │
│  │ Membership Type │ ☐                │ ☑️                │      │
│  └─────────────────┴──────────────────┴──────────────────┘      │
│                                                                  │
│  [Save Visibility Settings]                                      │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### 6.9 System Settings Tab

```
┌──────────────────────────────────────────────────────────────────┐
│  System Settings                                                 │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  MAINTENANCE                                                     │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  ☐ Enable Maintenance Mode                                       │
│                                                                  │
│  Maintenance Message:                                            │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ The portal is currently undergoing maintenance.          │   │
│  │ Please check back soon.                                  │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  SECURITY                                                        │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  Session Timeout (hours):                                        │
│  ┌────────────┐                                                  │
│  │ 168        │ (7 days)                                        │
│  └────────────┘                                                  │
│                                                                  │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  FILE UPLOADS                                                    │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  Max Upload Size (MB):                                           │
│  ┌────────────┐                                                  │
│  │ 50         │                                                  │
│  └────────────┘                                                  │
│                                                                  │
│  Allowed File Types:                                             │
│  [PDF] [DOC] [DOCX] [XLS] [XLSX] [PPT] [PPTX]                   │
│  [TXT] [JPG] [PNG] [WEBP] [+ Add Type]                          │
│                                                                  │
│  [Save System Settings]                                          │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### 6.10 Settings Access Pattern

```typescript
// src/lib/server/settings.ts

// Get a single setting with type safety
export async function getSetting<T>(
  supabase: SupabaseClient,
  category: string,
  key: string,
  defaultValue: T
): Promise<T> {
  const { data } = await supabase
    .from('app_settings')
    .select('setting_value')
    .eq('category', category)
    .eq('setting_key', key)
    .single();

  return data?.setting_value ?? defaultValue;
}

// Get all settings for a category
export async function getCategorySettings(
  supabase: SupabaseClient,
  category: string
): Promise<Record<string, any>> {
  const { data } = await supabase
    .from('app_settings')
    .select('setting_key, setting_value')
    .eq('category', category);

  return Object.fromEntries(
    (data ?? []).map(s => [s.setting_key, s.setting_value])
  );
}

// Update a setting (admin only)
export async function updateSetting(
  supabase: SupabaseClient,
  category: string,
  key: string,
  value: any,
  userId: string
): Promise<void> {
  await supabase
    .from('app_settings')
    .update({
      setting_value: value,
      updated_at: new Date().toISOString(),
      updated_by: userId
    })
    .eq('category', category)
    .eq('setting_key', key);
}
```

### 6.11 Settings Permissions

| Action | Member | Board | Admin |
|--------|--------|-------|-------|
| View public settings | ✓ | ✓ | ✓ |
| View all settings | - | - | ✓ |
| Edit settings | - | - | ✓ |
| Manage statuses | - | - | ✓ |
| Manage membership types | - | - | ✓ |
| Manage event types | - | - | ✓ |
| Manage document categories | - | - | ✓ |
| View settings audit log | - | - | ✓ |

---

## 7. EMAIL SYSTEM (Detailed)

### 7.1 Email Architecture

**Provider**: Supabase Edge Functions + external SMTP (Resend, SendGrid, or Mailgun)

**Why external SMTP:**
- Supabase built-in email is limited to auth emails only
- External SMTP provides better deliverability, tracking, and templates
- Resend recommended for simplicity and modern API

### 7.2 Email Provider Configuration

```sql
-- Email settings (stored in app_settings)
INSERT INTO app_settings (category, setting_key, setting_value, setting_type, display_name, description) VALUES
  ('email', 'provider', '"resend"', 'text', 'Email Provider', 'Email service provider (resend, sendgrid, mailgun)'),
  ('email', 'api_key', '""', 'text', 'API Key', 'Email provider API key (stored securely)'),
  ('email', 'from_address', '"noreply@monacousa.org"', 'text', 'From Address', 'Default sender email address'),
  ('email', 'from_name', '"Monaco USA"', 'text', 'From Name', 'Default sender name'),
  ('email', 'reply_to', '"contact@monacousa.org"', 'text', 'Reply-To Address', 'Reply-to email address'),
  ('email', 'enable_tracking', 'true', 'boolean', 'Enable Tracking', 'Track email opens and clicks'),
  ('email', 'batch_size', '50', 'number', 'Batch Size', 'Max emails per batch send'),
  ('email', 'rate_limit_per_hour', '100', 'number', 'Rate Limit', 'Maximum emails per hour');
```

### 7.3 Email Types & Triggers

| Email Type | Trigger | Recipients | Automated |
|------------|---------|------------|-----------|
| `welcome` | New signup verified | New member | Yes |
| `email_verification` | Signup | New member | Yes (Supabase) |
| `password_reset` | Password reset request | Member | Yes (Supabase) |
| `dues_reminder` | X days before due | Member | Yes (cron) |
| `dues_due_today` | Due date | Member | Yes (cron) |
| `dues_overdue` | Every X days overdue | Member | Yes (cron) |
| `dues_lapsed` | Grace period ends | Member | Yes (cron) |
| `dues_received` | Payment recorded | Member | Yes |
| `event_created` | New event published | All/visibility | Optional |
| `event_reminder` | X hours before event | RSVP'd members | Yes (cron) |
| `event_updated` | Event details changed | RSVP'd members | Yes |
| `event_cancelled` | Event cancelled | RSVP'd members | Yes |
| `rsvp_confirmation` | RSVP submitted | Member | Yes |
| `waitlist_promoted` | Spot opens up | Waitlisted member | Yes |
| `member_invite` | Admin invites member | Invitee | Manual |
| `broadcast` | Admin sends message | Selected members | Manual |

### 7.4 Email Templates Schema

```sql
CREATE TABLE public.email_templates (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

  -- Template identification
  template_key TEXT UNIQUE NOT NULL,         -- 'dues_reminder', 'welcome', etc.
  template_name TEXT NOT NULL,               -- 'Dues Reminder Email'
  category TEXT NOT NULL,                    -- 'dues', 'events', 'system'

  -- Template content
  subject TEXT NOT NULL,                     -- Subject line with {{variables}}
  body_html TEXT NOT NULL,                   -- HTML body with {{variables}}
  body_text TEXT,                            -- Plain text fallback

  -- Settings
  is_active BOOLEAN DEFAULT TRUE,
  is_system BOOLEAN DEFAULT FALSE,           -- System templates can't be deleted

  -- Metadata
  variables_schema JSONB,                    -- Available variables documentation
  preview_data JSONB,                        -- Sample data for preview

  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  updated_by UUID REFERENCES public.members(id)
);

-- Default email templates
INSERT INTO email_templates (template_key, template_name, category, subject, body_html, is_system, variables_schema) VALUES

-- Welcome Email
('welcome', 'Welcome Email', 'system',
 'Welcome to Monaco USA, {{member_name}}!',
 '<!DOCTYPE html>
<html>
<head><style>body{font-family:Arial,sans-serif;}</style></head>
<body>
<div style="max-width:600px;margin:0 auto;padding:20px;">
  <img src="{{logo_url}}" alt="Monaco USA" style="max-width:150px;">
  <h1>Welcome to Monaco USA!</h1>
  <p>Dear {{member_name}},</p>
  <p>Thank you for joining Monaco USA! Your Member ID is <strong>{{member_id}}</strong>.</p>
  <p>To complete your membership, please pay your annual dues of <strong>€{{dues_amount}}</strong>.</p>
  <h3>Payment Details:</h3>
  <ul>
    <li>Bank: {{bank_name}}</li>
    <li>IBAN: {{iban}}</li>
    <li>Account Holder: {{account_holder}}</li>
    <li>Reference: {{member_id}}</li>
  </ul>
  <p><a href="{{portal_link}}" style="background:#dc2626;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;">Access Your Portal</a></p>
  <p>Best regards,<br>Monaco USA Team</p>
</div>
</body>
</html>',
 TRUE,
 '{"member_name":"string","member_id":"string","dues_amount":"number","bank_name":"string","iban":"string","account_holder":"string","portal_link":"string","logo_url":"string"}'
),

-- Dues Reminder
('dues_reminder', 'Dues Reminder', 'dues',
 'Your Monaco USA dues are due in {{days_until_due}} days',
 '<!DOCTYPE html>
<html>
<body style="font-family:Arial,sans-serif;">
<div style="max-width:600px;margin:0 auto;padding:20px;">
  <h1>Dues Reminder</h1>
  <p>Dear {{member_name}},</p>
  <p>This is a friendly reminder that your Monaco USA membership dues of <strong>€{{dues_amount}}</strong> are due on <strong>{{due_date}}</strong> ({{days_until_due}} days from now).</p>
  <h3>Payment Details:</h3>
  <ul>
    <li>IBAN: {{iban}}</li>
    <li>Account Holder: {{account_holder}}</li>
    <li>Reference: {{member_id}}</li>
  </ul>
  <p><a href="{{portal_link}}/payments" style="background:#dc2626;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;">View Payment Details</a></p>
  <p>Thank you for being a valued member!</p>
</div>
</body>
</html>',
 TRUE,
 '{"member_name":"string","member_id":"string","dues_amount":"number","due_date":"date","days_until_due":"number","iban":"string","account_holder":"string","portal_link":"string"}'
),

-- Dues Overdue
('dues_overdue', 'Dues Overdue Notice', 'dues',
 'OVERDUE: Your Monaco USA dues are {{days_overdue}} days past due',
 '<!DOCTYPE html>
<html>
<body style="font-family:Arial,sans-serif;">
<div style="max-width:600px;margin:0 auto;padding:20px;">
  <h1 style="color:#dc2626;">Payment Overdue</h1>
  <p>Dear {{member_name}},</p>
  <p>Your Monaco USA membership dues of <strong>€{{dues_amount}}</strong> are now <strong>{{days_overdue}} days overdue</strong>.</p>
  <p>Please make your payment as soon as possible to maintain your membership benefits.</p>
  {{#if grace_period_remaining}}
  <p><strong>Note:</strong> You have {{grace_period_remaining}} days remaining in your grace period before your membership is set to inactive.</p>
  {{/if}}
  <h3>Payment Details:</h3>
  <ul>
    <li>IBAN: {{iban}}</li>
    <li>Account Holder: {{account_holder}}</li>
    <li>Reference: {{member_id}}</li>
  </ul>
  <p><a href="{{portal_link}}/payments" style="background:#dc2626;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;">Pay Now</a></p>
</div>
</body>
</html>',
 TRUE,
 '{"member_name":"string","member_id":"string","dues_amount":"number","days_overdue":"number","grace_period_remaining":"number","iban":"string","account_holder":"string","portal_link":"string"}'
),

-- Dues Received
('dues_received', 'Payment Confirmation', 'dues',
 'Thank you! Your Monaco USA dues payment has been received',
 '<!DOCTYPE html>
<html>
<body style="font-family:Arial,sans-serif;">
<div style="max-width:600px;margin:0 auto;padding:20px;">
  <h1 style="color:#10b981;">Payment Received!</h1>
  <p>Dear {{member_name}},</p>
  <p>Thank you! We have received your membership dues payment.</p>
  <h3>Payment Details:</h3>
  <ul>
    <li>Amount: €{{amount_paid}}</li>
    <li>Payment Date: {{payment_date}}</li>
    <li>Next Due Date: {{next_due_date}}</li>
    <li>Reference: {{payment_reference}}</li>
  </ul>
  <p>Your membership is now active until {{next_due_date}}.</p>
  <p><a href="{{portal_link}}/payments" style="background:#10b981;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;">View Payment History</a></p>
</div>
</body>
</html>',
 TRUE,
 '{"member_name":"string","amount_paid":"number","payment_date":"date","next_due_date":"date","payment_reference":"string","portal_link":"string"}'
),

-- Event RSVP Confirmation
('rsvp_confirmation', 'RSVP Confirmation', 'events',
 'You''re registered: {{event_title}}',
 '<!DOCTYPE html>
<html>
<body style="font-family:Arial,sans-serif;">
<div style="max-width:600px;margin:0 auto;padding:20px;">
  <h1>You''re Registered!</h1>
  <p>Dear {{member_name}},</p>
  <p>Your RSVP for <strong>{{event_title}}</strong> has been confirmed.</p>
  <h3>Event Details:</h3>
  <ul>
    <li><strong>Date:</strong> {{event_date}}</li>
    <li><strong>Time:</strong> {{event_time}}</li>
    <li><strong>Location:</strong> {{event_location}}</li>
    {{#if guest_count}}<li><strong>Additional Guests:</strong> {{guest_count}}</li>{{/if}}
  </ul>
  {{#if is_paid}}
  <h3>Payment Required:</h3>
  <p>Total: €{{total_amount}}</p>
  <ul>
    <li>IBAN: {{iban}}</li>
    <li>Reference: {{payment_reference}}</li>
  </ul>
  {{/if}}
  <p><a href="{{portal_link}}/events/{{event_id}}" style="background:#dc2626;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;">View Event</a></p>
</div>
</body>
</html>',
 TRUE,
 '{"member_name":"string","event_title":"string","event_date":"date","event_time":"string","event_location":"string","guest_count":"number","is_paid":"boolean","total_amount":"number","iban":"string","payment_reference":"string","event_id":"string","portal_link":"string"}'
),

-- Event Reminder
('event_reminder', 'Event Reminder', 'events',
 'Reminder: {{event_title}} is {{time_until_event}}',
 '<!DOCTYPE html>
<html>
<body style="font-family:Arial,sans-serif;">
<div style="max-width:600px;margin:0 auto;padding:20px;">
  <h1>Event Reminder</h1>
  <p>Dear {{member_name}},</p>
  <p>This is a reminder that <strong>{{event_title}}</strong> is {{time_until_event}}.</p>
  <h3>Event Details:</h3>
  <ul>
    <li><strong>Date:</strong> {{event_date}}</li>
    <li><strong>Time:</strong> {{event_time}}</li>
    <li><strong>Location:</strong> {{event_location}}</li>
  </ul>
  {{#if event_description}}
  <p>{{event_description}}</p>
  {{/if}}
  <p>We look forward to seeing you there!</p>
</div>
</body>
</html>',
 TRUE,
 '{"member_name":"string","event_title":"string","event_date":"date","event_time":"string","event_location":"string","event_description":"string","time_until_event":"string"}'
);
```

### 7.5 Email Logging Schema

```sql
-- Enhanced email logs with tracking
CREATE TABLE public.email_logs (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

  -- Recipients
  recipient_id UUID REFERENCES public.members(id),
  recipient_email TEXT NOT NULL,
  recipient_name TEXT,

  -- Email details
  template_key TEXT REFERENCES public.email_templates(template_key),
  subject TEXT NOT NULL,
  email_type TEXT NOT NULL,

  -- Status tracking
  status TEXT NOT NULL DEFAULT 'queued'
    CHECK (status IN ('queued', 'sent', 'delivered', 'opened', 'clicked', 'bounced', 'failed')),

  -- Provider data
  provider TEXT,                             -- 'resend', 'sendgrid', etc.
  provider_message_id TEXT,                  -- External message ID for tracking

  -- Engagement tracking
  opened_at TIMESTAMPTZ,
  clicked_at TIMESTAMPTZ,

  -- Error handling
  error_message TEXT,
  retry_count INTEGER DEFAULT 0,

  -- Metadata
  template_variables JSONB,                  -- Variables used in template
  sent_by UUID REFERENCES public.members(id), -- For manual sends

  -- Timestamps
  created_at TIMESTAMPTZ DEFAULT NOW(),
  sent_at TIMESTAMPTZ,
  delivered_at TIMESTAMPTZ
);

-- Index for common queries
CREATE INDEX idx_email_logs_recipient ON public.email_logs(recipient_id);
CREATE INDEX idx_email_logs_status ON public.email_logs(status);
CREATE INDEX idx_email_logs_type ON public.email_logs(email_type);
CREATE INDEX idx_email_logs_created ON public.email_logs(created_at DESC);
```

### 7.6 Automated Email Scheduler (Supabase Edge Function)

```typescript
// supabase/functions/email-scheduler/index.ts

import { createClient } from '@supabase/supabase-js';
import { Resend } from 'resend';

// Runs daily via pg_cron
Deno.serve(async (req) => {
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  );

  const today = new Date();
  today.setHours(0, 0, 0, 0);

  // 1. Get settings
  const settings = await getSettings(supabase);
  const reminderDays = settings.reminder_days_before as number[];
  const gracePeriod = settings.grace_period_days as number;

  // 2. Find members needing reminders
  const { data: membersWithDues } = await supabase
    .from('members_with_dues')
    .select('*')
    .in('dues_status', ['current', 'due_soon', 'overdue']);

  for (const member of membersWithDues || []) {
    const dueDate = new Date(member.current_due_date);
    const daysUntil = Math.ceil((dueDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
    const daysOverdue = member.days_overdue || 0;

    // Check if we need to send a reminder
    if (daysUntil > 0 && reminderDays.includes(daysUntil)) {
      // Send upcoming reminder
      await sendEmail(supabase, 'dues_reminder', member, {
        days_until_due: daysUntil
      });
    } else if (daysUntil === 0) {
      // Due today
      await sendEmail(supabase, 'dues_due_today', member, {});
    } else if (daysOverdue > 0 && daysOverdue <= gracePeriod) {
      // Overdue but in grace period
      if (daysOverdue % settings.overdue_reminder_interval === 0) {
        await sendEmail(supabase, 'dues_overdue', member, {
          days_overdue: daysOverdue,
          grace_period_remaining: gracePeriod - daysOverdue
        });
      }
    } else if (daysOverdue === gracePeriod + 1) {
      // Grace period just ended
      await sendEmail(supabase, 'dues_lapsed', member, {});
    }
  }

  // 3. Send event reminders
  await sendEventReminders(supabase, settings);

  return new Response(JSON.stringify({ success: true }), {
    headers: { 'Content-Type': 'application/json' }
  });
});

async function sendEmail(
  supabase: any,
  templateKey: string,
  member: any,
  extraVariables: Record<string, any>
) {
  // Get template
  const { data: template } = await supabase
    .from('email_templates')
    .select('*')
    .eq('template_key', templateKey)
    .eq('is_active', true)
    .single();

  if (!template) return;

  // Build variables
  const variables = {
    member_name: `${member.first_name} ${member.last_name}`,
    member_id: member.member_id,
    dues_amount: member.annual_dues || 50,
    due_date: member.current_due_date,
    portal_link: Deno.env.get('PORTAL_URL'),
    ...extraVariables
  };

  // Add payment settings
  const settings = await getSettings(supabase);
  variables.iban = settings.payment_iban;
  variables.account_holder = settings.payment_account_holder;
  variables.bank_name = settings.payment_bank_name;

  // Render template
  const subject = renderTemplate(template.subject, variables);
  const html = renderTemplate(template.body_html, variables);

  // Send via provider
  const resend = new Resend(Deno.env.get('RESEND_API_KEY'));
  const result = await resend.emails.send({
    from: `${settings.from_name} <${settings.from_address}>`,
    to: member.email,
    subject,
    html
  });

  // Log email
  await supabase.from('email_logs').insert({
    recipient_id: member.id,
    recipient_email: member.email,
    recipient_name: variables.member_name,
    template_key: templateKey,
    subject,
    email_type: templateKey,
    status: result.error ? 'failed' : 'sent',
    provider: 'resend',
    provider_message_id: result.data?.id,
    error_message: result.error?.message,
    template_variables: variables,
    sent_at: new Date().toISOString()
  });
}
```

### 7.7 Email Settings UI (Admin)

```
┌──────────────────────────────────────────────────────────────────┐
│  Email Settings                                                  │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  PROVIDER CONFIGURATION                                          │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  Email Provider:                                                 │
│  ┌────────────────────────────────────────┐                     │
│  │ Resend                              ▼  │                     │
│  └────────────────────────────────────────┘                     │
│                                                                  │
│  API Key:                                                        │
│  ┌────────────────────────────────────────┐                     │
│  │ re_•••••••••••••••                     │ [Test Connection]   │
│  └────────────────────────────────────────┘                     │
│                                                                  │
│  From Address:                                                   │
│  ┌────────────────────────────────────────┐                     │
│  │ noreply@monacousa.org                  │                     │
│  └────────────────────────────────────────┘                     │
│                                                                  │
│  From Name:                                                      │
│  ┌────────────────────────────────────────┐                     │
│  │ Monaco USA                             │                     │
│  └────────────────────────────────────────┘                     │
│                                                                  │
│  Reply-To:                                                       │
│  ┌────────────────────────────────────────┐                     │
│  │ contact@monacousa.org                  │                     │
│  └────────────────────────────────────────┘                     │
│                                                                  │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  TRACKING                                                        │
│  ─────────────────────────────────────────────────────────────  │
│                                                                  │
│  ☑️ Enable open tracking                                         │
│  ☑️ Enable click tracking                                        │
│                                                                  │
│  [Save Email Settings]                                           │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### 7.8 Email Templates Editor (Admin)

```
┌──────────────────────────────────────────────────────────────────┐
│  Email Templates                                                 │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌───────────────────┬────────────────┬─────────┬──────────────┐ │
│  │ Template          │ Category       │ Active  │ Actions      │ │
│  ├───────────────────┼────────────────┼─────────┼──────────────┤ │
│  │ Welcome Email     │ System         │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Dues Reminder     │ Dues           │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Dues Due Today    │ Dues           │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Dues Overdue      │ Dues           │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Dues Lapsed       │ Dues           │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Dues Received     │ Dues           │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ RSVP Confirmation │ Events         │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Event Reminder    │ Events         │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Event Cancelled   │ Events         │ ✓       │ ✏️ 👁️ 📧      │ │
│  │ Waitlist Promoted │ Events         │ ✓       │ ✏️ 👁️ 📧      │ │
│  └───────────────────┴────────────────┴─────────┴──────────────┘ │
│                                                                  │
│  Legend: ✏️ Edit  |  👁️ Preview  |  📧 Send Test                │
│                                                                  │
│  [+ Create Custom Template]                                      │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### 7.9 Template Editor View

```
┌──────────────────────────────────────────────────────────────────┐
│  Edit Template: Dues Reminder                                    │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Template Name:                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ Dues Reminder                                             │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  Subject Line:                                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ Your Monaco USA dues are due in {{days_until_due}} days   │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌──────────────────────┬───────────────────────────────────┐   │
│  │ HTML Editor          │ Preview                           │   │
│  ├──────────────────────┼───────────────────────────────────┤   │
│  │ <h1>Dues Reminder</h1│                                   │   │
│  │ <p>Dear {{member_nam │  ┌─────────────────────────────┐  │   │
│  │ e}},</p>             │  │     Dues Reminder           │  │   │
│  │ <p>This is a friendl │  │                             │  │   │
│  │ y reminder that your │  │  Dear John Doe,             │  │   │
│  │ Monaco USA membershi │  │                             │  │   │
│  │ p dues...</p>        │  │  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 |
