194 lines
6.3 KiB
TypeScript
194 lines
6.3 KiB
TypeScript
/**
|
|
* Shared registration helpers used by both the signup and join pages.
|
|
* Consolidates member creation and welcome email logic.
|
|
*/
|
|
|
|
import { supabaseAdmin } from '$lib/server/supabase';
|
|
import { sendTemplatedEmail } from '$lib/server/email';
|
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
|
|
// ────────────────────────────────────────────────────────────────
|
|
// Types
|
|
// ────────────────────────────────────────────────────────────────
|
|
|
|
export interface RegistrationData {
|
|
userId: string;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
phone?: string;
|
|
dateOfBirth?: string;
|
|
address?: string;
|
|
nationality?: string[];
|
|
}
|
|
|
|
export interface CreateMemberResult {
|
|
success: boolean;
|
|
error?: string;
|
|
memberId?: string;
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────
|
|
// Member Creation
|
|
// ────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Create a member record in the database after auth user creation.
|
|
*
|
|
* Handles:
|
|
* - Looking up the default/pending membership status and type
|
|
* - Member ID auto-generated by database trigger (atomic sequence)
|
|
* - Inserting the member record
|
|
*
|
|
* @param data Core registration data.
|
|
* @param supabase The Supabase client to use for DB operations.
|
|
* @param options Additional options for how the member is created.
|
|
*/
|
|
export async function createMemberRecord(
|
|
data: RegistrationData,
|
|
supabase: SupabaseClient,
|
|
options?: {
|
|
/** Look up status by name instead of is_default. Defaults to undefined (uses is_default). */
|
|
statusName?: string;
|
|
/** @deprecated Member ID now auto-generated by database trigger. */
|
|
generateMemberId?: boolean;
|
|
}
|
|
): Promise<CreateMemberResult> {
|
|
const statusName = options?.statusName;
|
|
|
|
// Look up the membership status
|
|
let statusQuery;
|
|
if (statusName) {
|
|
statusQuery = supabase
|
|
.from('membership_statuses')
|
|
.select('id')
|
|
.eq('name', statusName)
|
|
.single();
|
|
} else {
|
|
statusQuery = supabase
|
|
.from('membership_statuses')
|
|
.select('id')
|
|
.eq('is_default', true)
|
|
.single();
|
|
}
|
|
|
|
const { data: statusData, error: statusError } = await statusQuery;
|
|
|
|
if (statusError || !statusData?.id) {
|
|
console.error('No membership status found:', statusError);
|
|
return { success: false, error: 'System configuration error. Please contact support.' };
|
|
}
|
|
|
|
// Look up the default membership type
|
|
const { data: typeData, error: typeError } = await supabase
|
|
.from('membership_types')
|
|
.select('id')
|
|
.eq('is_default', true)
|
|
.single();
|
|
|
|
if (typeError || !typeData?.id) {
|
|
console.error('No default membership type found:', typeError);
|
|
return { success: false, error: 'System configuration error. Please contact support.' };
|
|
}
|
|
|
|
// Member ID will be auto-generated by database trigger (generate_member_id)
|
|
// See migration 018_atomic_member_id_generation.sql
|
|
|
|
// Create the member profile
|
|
const insertPayload: Record<string, unknown> = {
|
|
id: data.userId,
|
|
first_name: data.firstName,
|
|
last_name: data.lastName,
|
|
email: data.email,
|
|
phone: data.phone || null,
|
|
date_of_birth: data.dateOfBirth || null,
|
|
address: data.address || null,
|
|
nationality: data.nationality || [],
|
|
role: 'member',
|
|
membership_status_id: statusData.id,
|
|
membership_type_id: typeData.id
|
|
};
|
|
|
|
// member_id is auto-generated by trigger, no need to set it
|
|
|
|
const { error: memberError, data: insertedMember } = await supabase
|
|
.from('members')
|
|
.insert(insertPayload)
|
|
.select('member_id')
|
|
.single();
|
|
|
|
if (memberError) {
|
|
console.error('Failed to create member profile:', memberError);
|
|
return { success: false, error: 'Failed to create member profile. Please try again or contact support.' };
|
|
}
|
|
|
|
return { success: true, memberId: insertedMember?.member_id };
|
|
}
|
|
|
|
/**
|
|
* Clean up auth user on registration failure.
|
|
* Uses supabaseAdmin to ensure we can always delete the user.
|
|
*/
|
|
export async function cleanupAuthUser(userId: string): Promise<void> {
|
|
try {
|
|
await supabaseAdmin.auth.admin.deleteUser(userId);
|
|
} catch (deleteError) {
|
|
console.error('Failed to clean up auth user:', deleteError);
|
|
}
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────
|
|
// Welcome Email
|
|
// ────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Send the onboarding welcome email with payment instructions.
|
|
*
|
|
* @param member Basic member info for the email template.
|
|
* @param paymentSettings Payment account details from app_settings.
|
|
* @param duesAmount The annual dues amount.
|
|
* @param paymentDeadline The payment deadline date.
|
|
*/
|
|
export async function sendWelcomeEmail(
|
|
member: {
|
|
id: string;
|
|
first_name: string;
|
|
email: string;
|
|
member_id?: string;
|
|
},
|
|
paymentSettings: Record<string, string>,
|
|
duesAmount: number,
|
|
paymentDeadline: Date
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const result = await sendTemplatedEmail(
|
|
'onboarding_welcome',
|
|
member.email,
|
|
{
|
|
first_name: member.first_name,
|
|
member_id: member.member_id || 'N/A',
|
|
amount: `\u20AC${duesAmount}`,
|
|
payment_deadline: paymentDeadline.toLocaleDateString('en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
}),
|
|
account_holder: paymentSettings.account_holder || 'Monaco USA',
|
|
bank_name: paymentSettings.bank_name || 'Credit Foncier de Monaco',
|
|
iban: paymentSettings.iban || 'Contact for details'
|
|
},
|
|
{
|
|
recipientId: member.id,
|
|
recipientName: member.first_name,
|
|
sentBy: 'system'
|
|
}
|
|
);
|
|
|
|
return result;
|
|
} catch (emailError) {
|
|
console.error('Failed to send welcome email:', emailError);
|
|
return { success: false, error: 'Failed to send welcome email' };
|
|
}
|
|
}
|