Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/app/api/volunteers/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NextResponse } from 'next/server';
import type { Volunteer } from '@/types/volunteer';

// In-memory store for edge runtime (production would use a DB)
const volunteers: Volunteer[] = [];

export const runtime = 'edge';

export async function GET() {
return NextResponse.json({ data: volunteers, total: volunteers.length });
}

export async function POST(request: Request) {
let body: unknown;
try {
body = await request.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
}

const { name, email, role, status, sms } = body as Partial<Volunteer>;

if (!name || !email || !role) {
return NextResponse.json({ error: 'name, email, and role are required' }, { status: 400 });
}

const volunteer: Volunteer = {
id: `vol_${Math.random().toString(36).slice(2)}_${Date.now()}`,
name,
email,
role,
status: status ?? 'pending',
sms: sms ?? { optedIn: false, phoneNumber: '', categories: [] },
joinedAt: new Date().toISOString(),
};

volunteers.push(volunteer);
return NextResponse.json({ data: volunteer }, { status: 201 });
}
65 changes: 65 additions & 0 deletions src/app/store/volunteerStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { create } from 'zustand';
import type { Volunteer, VolunteerSMSPreferences } from '@/types/volunteer';

const STORAGE_KEY = 'volunteers_v1';

function load<T>(key: string, fallback: T): T {
if (typeof window === 'undefined') return fallback;
try {
const raw = localStorage.getItem(key);
return raw ? (JSON.parse(raw) as T) : fallback;
} catch {
return fallback;
}
}

function save<T>(key: string, value: T) {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {}
}

interface VolunteerState {
volunteers: Volunteer[];
addVolunteer: (v: Omit<Volunteer, 'id' | 'joinedAt'>) => Volunteer;
updateVolunteer: (id: string, patch: Partial<Omit<Volunteer, 'id'>>) => void;
removeVolunteer: (id: string) => void;
updateSMSPreferences: (id: string, sms: Partial<VolunteerSMSPreferences>) => void;
}

export const useVolunteerStore = create<VolunteerState>((set, get) => ({
volunteers: load<Volunteer[]>(STORAGE_KEY, []),

addVolunteer: (v) => {
const volunteer: Volunteer = {
...v,
id: `vol_${Math.random().toString(36).slice(2)}_${Date.now()}`,
joinedAt: new Date().toISOString(),
};
const next = [...get().volunteers, volunteer];
set({ volunteers: next });
save(STORAGE_KEY, next);
return volunteer;
},

updateVolunteer: (id, patch) => {
const next = get().volunteers.map((v) => (v.id === id ? { ...v, ...patch } : v));
set({ volunteers: next });
save(STORAGE_KEY, next);
},

removeVolunteer: (id) => {
const next = get().volunteers.filter((v) => v.id !== id);
set({ volunteers: next });
save(STORAGE_KEY, next);
},

updateSMSPreferences: (id, sms) => {
const next = get().volunteers.map((v) =>
v.id === id ? { ...v, sms: { ...v.sms, ...sms } } : v,
);
set({ volunteers: next });
save(STORAGE_KEY, next);
},
}));
26 changes: 26 additions & 0 deletions src/types/volunteer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export type VolunteerRole = 'mentor' | 'moderator' | 'content_reviewer' | 'event_coordinator';

export type VolunteerStatus = 'active' | 'inactive' | 'pending';

export interface VolunteerSMSPreferences {
optedIn: boolean;
phoneNumber: string;
/** Notification categories the volunteer wants via SMS */
categories: Array<'assignment' | 'reminder' | 'urgent' | 'general'>;
}

export interface Volunteer {
id: string;
name: string;
email: string;
role: VolunteerRole;
status: VolunteerStatus;
sms: VolunteerSMSPreferences;
joinedAt: string; // ISO
}

export interface VolunteerSMSPayload {
volunteerId: string;
category: VolunteerSMSPreferences['categories'][number];
message: string;
}
Loading