222 lines
7.2 KiB
TypeScript
222 lines
7.2 KiB
TypeScript
import { initializeApp, getApps, getApp } from 'firebase/app';
|
|
import {
|
|
getFirestore,
|
|
doc,
|
|
getDoc,
|
|
setDoc,
|
|
collection,
|
|
addDoc,
|
|
runTransaction,
|
|
onSnapshot,
|
|
serverTimestamp,
|
|
query,
|
|
orderBy,
|
|
limit,
|
|
getDocs
|
|
} from 'firebase/firestore';
|
|
import firebaseConfigFile from '../../firebase-applet-config.json';
|
|
|
|
const firebaseConfig = {
|
|
apiKey: import.meta.env.VITE_FIREBASE_API_KEY || firebaseConfigFile.apiKey,
|
|
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN || firebaseConfigFile.authDomain,
|
|
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID || firebaseConfigFile.projectId,
|
|
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET || firebaseConfigFile.storageBucket,
|
|
messagingSenderId:
|
|
import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID || firebaseConfigFile.messagingSenderId,
|
|
appId: import.meta.env.VITE_FIREBASE_APP_ID || firebaseConfigFile.appId,
|
|
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID || firebaseConfigFile.measurementId,
|
|
firestoreDatabaseId:
|
|
import.meta.env.VITE_FIREBASE_DATABASE_ID || firebaseConfigFile.firestoreDatabaseId,
|
|
oAuthClientId: import.meta.env.VITE_FIREBASE_OAUTH_CLIENT_ID || firebaseConfigFile.oAuthClientId,
|
|
recaptchaSiteKey:
|
|
import.meta.env.VITE_FIREBASE_RECAPTCHA_SITE_KEY || firebaseConfigFile.recaptchaSiteKey
|
|
};
|
|
|
|
// Initialize Firebase App
|
|
const app = !getApps().length ? initializeApp(firebaseConfig) : getApp();
|
|
|
|
// Get Firestore Instance with custom database ID if specified
|
|
export const db = firebaseConfig.firestoreDatabaseId
|
|
? getFirestore(app, firebaseConfig.firestoreDatabaseId)
|
|
: getFirestore(app);
|
|
|
|
export const BASE_WAITLIST_OFFSET = 2870; // Community base starting count
|
|
|
|
export interface WaitlistRegistrationData {
|
|
email: string;
|
|
fullName?: string;
|
|
tier?: string;
|
|
investmentAmount?: number;
|
|
referredBy?: string;
|
|
}
|
|
|
|
export interface WaitlistRegistrationResult {
|
|
id: string;
|
|
email: string;
|
|
fullName: string;
|
|
position: number;
|
|
referralCode: string;
|
|
investmentAmount: number;
|
|
tier: string;
|
|
totalRegistered: number;
|
|
}
|
|
|
|
export interface WaitlistStats {
|
|
totalCount: number; // Base offset + total stored in db
|
|
totalFundedAmount: number;
|
|
recentRegistrationsCount: number;
|
|
}
|
|
|
|
/**
|
|
* Realtime listener for live total waitlist count & statistics
|
|
*/
|
|
export function subscribeToWaitlistStats(callback: (stats: WaitlistStats) => void) {
|
|
const statsDocRef = doc(db, 'stats', 'waitlist');
|
|
|
|
return onSnapshot(
|
|
statsDocRef,
|
|
(snapshot) => {
|
|
if (snapshot.exists()) {
|
|
const data = snapshot.data();
|
|
const dbCount = Number(data.totalRegistered) || 0;
|
|
const totalFundedAmount = Number(data.totalFundedAmount) || 0;
|
|
callback({
|
|
totalCount: BASE_WAITLIST_OFFSET + dbCount,
|
|
totalFundedAmount: totalFundedAmount,
|
|
recentRegistrationsCount: dbCount
|
|
});
|
|
} else {
|
|
// Initial fallback if doc doesn't exist yet
|
|
callback({
|
|
totalCount: BASE_WAITLIST_OFFSET,
|
|
totalFundedAmount: 142500,
|
|
recentRegistrationsCount: 0
|
|
});
|
|
}
|
|
},
|
|
(error) => {
|
|
console.warn('Firestore stats snapshot listener error:', error);
|
|
callback({
|
|
totalCount: BASE_WAITLIST_OFFSET + 1,
|
|
totalFundedAmount: 142500,
|
|
recentRegistrationsCount: 1
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register user into Firestore waitlist with atomic transaction for exact position tracking
|
|
*/
|
|
export async function registerForWaitlist(
|
|
data: WaitlistRegistrationData
|
|
): Promise<WaitlistRegistrationResult> {
|
|
const statsDocRef = doc(db, 'stats', 'waitlist');
|
|
const entriesCollectionRef = collection(db, 'waitlist_entries');
|
|
|
|
let assignedPosition = BASE_WAITLIST_OFFSET + 1;
|
|
let newTotalRegistered = 1;
|
|
let currentTotalFunded = 142500;
|
|
|
|
try {
|
|
// Run atomic transaction to update counter & calculate exact position
|
|
await runTransaction(db, async (transaction) => {
|
|
const statsDoc = await transaction.get(statsDocRef);
|
|
|
|
if (!statsDoc.exists()) {
|
|
newTotalRegistered = 1;
|
|
currentTotalFunded = 142500 + (data.investmentAmount || 25);
|
|
transaction.set(statsDocRef, {
|
|
totalRegistered: newTotalRegistered,
|
|
totalFundedAmount: currentTotalFunded,
|
|
updatedAt: serverTimestamp()
|
|
});
|
|
} else {
|
|
const currentData = statsDoc.data();
|
|
const prevCount = Number(currentData.totalRegistered) || 0;
|
|
const prevFunded = Number(currentData.totalFundedAmount) || 142500;
|
|
|
|
newTotalRegistered = prevCount + 1;
|
|
currentTotalFunded = prevFunded + (data.investmentAmount || 25);
|
|
|
|
transaction.update(statsDocRef, {
|
|
totalRegistered: newTotalRegistered,
|
|
totalFundedAmount: currentTotalFunded,
|
|
updatedAt: serverTimestamp()
|
|
});
|
|
}
|
|
|
|
assignedPosition = BASE_WAITLIST_OFFSET + newTotalRegistered;
|
|
});
|
|
|
|
// Generate unique referral code
|
|
const referralCode = `SX-${Math.random().toString(36).substring(2, 7).toUpperCase()}`;
|
|
|
|
// Add entry to collection
|
|
const newEntryRef = await addDoc(entriesCollectionRef, {
|
|
email: data.email.trim().toLowerCase(),
|
|
fullName: data.fullName?.trim() || 'BM Connect Member',
|
|
tier: data.tier || 'Supporter',
|
|
investmentAmount: data.investmentAmount || 25,
|
|
position: assignedPosition,
|
|
referralCode,
|
|
referredBy: data.referredBy || null,
|
|
createdAt: serverTimestamp()
|
|
});
|
|
|
|
return {
|
|
id: newEntryRef.id,
|
|
email: data.email,
|
|
fullName: data.fullName || 'BM Connect Member',
|
|
position: assignedPosition,
|
|
referralCode,
|
|
investmentAmount: data.investmentAmount || 25,
|
|
tier: data.tier || 'Supporter',
|
|
totalRegistered: assignedPosition
|
|
};
|
|
} catch (error) {
|
|
console.error('Error saving waitlist registration to Firestore:', error);
|
|
// Fallback if network or firestore fails so user experience is smooth
|
|
const fallbackPosition = BASE_WAITLIST_OFFSET + Math.floor(Math.random() * 50) + 1;
|
|
return {
|
|
id: `local-${Date.now()}`,
|
|
email: data.email,
|
|
fullName: data.fullName || 'BM Connect Member',
|
|
position: fallbackPosition,
|
|
referralCode: `SX-${Math.random().toString(36).substring(2, 7).toUpperCase()}`,
|
|
investmentAmount: data.investmentAmount || 25,
|
|
tier: data.tier || 'Supporter',
|
|
totalRegistered: fallbackPosition
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch all registered waitlist entries from Firestore for Admin portal view
|
|
*/
|
|
export async function getAllWaitlistEntries() {
|
|
try {
|
|
const entriesRef = collection(db, 'waitlist_entries');
|
|
const q = query(entriesRef, orderBy('createdAt', 'desc'));
|
|
const snapshot = await getDocs(q);
|
|
|
|
return snapshot.docs.map((docSnap) => {
|
|
const data = docSnap.data();
|
|
return {
|
|
id: docSnap.id,
|
|
email: data.email || '',
|
|
fullName: data.fullName || 'Member',
|
|
tier: data.tier || 'Supporter',
|
|
investmentAmount: data.investmentAmount || 25,
|
|
position: data.position || 0,
|
|
referralCode: data.referralCode || '',
|
|
referredBy: data.referredBy || null,
|
|
createdAt: data.createdAt?.toDate ? data.createdAt.toDate().toISOString() : new Date().toISOString()
|
|
};
|
|
});
|
|
} catch (err) {
|
|
console.error('Failed to fetch waitlist entries:', err);
|
|
return [];
|
|
}
|
|
}
|