feat: Complete platform enhancement with multi-tenant architecture
Major additions: - Territory manager system with application workflow - Custom pricing and page builder with Craft.js - Enhanced Stripe Connect onboarding - CodeReadr QR scanning integration - Kiosk mode for venue sales - Super admin dashboard and analytics - MCP integration for AI-powered operations Infrastructure improvements: - Centralized API client and routing system - Enhanced authentication with organization context - Comprehensive theme management system - Advanced event management with custom tabs - Performance monitoring and accessibility features Database schema updates: - Territory management tables - Custom pages and pricing structures - Kiosk PIN system - Enhanced organization profiles - CodeReadr integration tables 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
561
src/lib/territory-manager-router.ts
Normal file
561
src/lib/territory-manager-router.ts
Normal file
@@ -0,0 +1,561 @@
|
||||
import { territoryManagerAPI } from './territory-manager-api';
|
||||
import { TerritoryManagerAuth } from './territory-manager-auth';
|
||||
import type {
|
||||
TerritoryManager,
|
||||
TerritoryManagerStats,
|
||||
TerritoryApplication,
|
||||
Lead,
|
||||
Commission,
|
||||
Achievement,
|
||||
ExpenseReport,
|
||||
TMNotification,
|
||||
ReferralLink,
|
||||
MarketingMaterial,
|
||||
LeaderboardEntry,
|
||||
ApplicationFormData,
|
||||
LeadFormData,
|
||||
ExpenseReportFormData
|
||||
} from './territory-manager-types';
|
||||
|
||||
/**
|
||||
* Centralized Territory Manager API router
|
||||
* Browser-friendly API router that handles all Territory Manager API calls
|
||||
* This is designed to be used in Astro client-side scripts and React components
|
||||
*/
|
||||
export class TerritoryManagerRouter {
|
||||
|
||||
/**
|
||||
* Authentication methods
|
||||
*/
|
||||
static async signIn(email: string, password: string) {
|
||||
try {
|
||||
return await TerritoryManagerAuth.signIn(email, password);
|
||||
} catch (error) {
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async signOut() {
|
||||
try {
|
||||
return await TerritoryManagerAuth.signOut();
|
||||
} catch (error) {
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async checkAuth(): Promise<{
|
||||
authenticated: boolean;
|
||||
user: any;
|
||||
territoryManager: TerritoryManager | null;
|
||||
}> {
|
||||
try {
|
||||
const isTM = await TerritoryManagerAuth.isTerritoryManager();
|
||||
const territoryManager = isTM ? await TerritoryManagerAuth.getCurrentTerritoryManager() : null;
|
||||
|
||||
return {
|
||||
authenticated: isTM,
|
||||
user: territoryManager ? { email: territoryManager.user_id } : null,
|
||||
territoryManager
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
authenticated: false,
|
||||
user: null,
|
||||
territoryManager: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard data loading
|
||||
*/
|
||||
static async loadDashboard(): Promise<{
|
||||
stats: TerritoryManagerStats | null;
|
||||
territoryManager: TerritoryManager | null;
|
||||
recent_activity: any[];
|
||||
active_leads: Lead[];
|
||||
achievements: Achievement[];
|
||||
notifications: TMNotification[];
|
||||
earnings_history: any[];
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) {
|
||||
return {
|
||||
stats: null,
|
||||
territoryManager: null,
|
||||
recent_activity: [],
|
||||
active_leads: [],
|
||||
achievements: [],
|
||||
notifications: [],
|
||||
earnings_history: [],
|
||||
error: 'Not authenticated as territory manager'
|
||||
};
|
||||
}
|
||||
|
||||
// Load all dashboard data in parallel
|
||||
const [
|
||||
stats,
|
||||
leads,
|
||||
achievements,
|
||||
notifications,
|
||||
commissions
|
||||
] = await Promise.all([
|
||||
territoryManagerAPI.getTerritoryManagerStats(territoryManager.id),
|
||||
territoryManagerAPI.getLeads(territoryManager.id),
|
||||
territoryManagerAPI.getAchievements(),
|
||||
territoryManagerAPI.getNotifications(territoryManager.id),
|
||||
territoryManagerAPI.getCommissions(territoryManager.id)
|
||||
]);
|
||||
|
||||
// Generate recent activity from commissions and leads
|
||||
const recent_activity = [
|
||||
...commissions.slice(0, 3).map(c => ({
|
||||
type: 'commission',
|
||||
message: `Earned $${c.total_commission} commission`,
|
||||
created_at: c.created_at
|
||||
})),
|
||||
...leads.slice(0, 2).map(l => ({
|
||||
type: 'lead',
|
||||
message: `${l.status === 'converted' ? 'Converted' : 'Added'} lead: ${l.event_name}`,
|
||||
created_at: l.created_at
|
||||
}))
|
||||
].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||
|
||||
// Generate earnings history from commissions
|
||||
const earnings_history = this.generateEarningsHistory(commissions);
|
||||
|
||||
return {
|
||||
stats,
|
||||
territoryManager,
|
||||
recent_activity,
|
||||
active_leads: leads.filter(l => l.status !== 'converted' && l.status !== 'dead'),
|
||||
achievements,
|
||||
notifications,
|
||||
earnings_history,
|
||||
error: null
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
stats: null,
|
||||
territoryManager: null,
|
||||
recent_activity: [],
|
||||
active_leads: [],
|
||||
achievements: [],
|
||||
notifications: [],
|
||||
earnings_history: [],
|
||||
error: 'Failed to load dashboard data'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Application management
|
||||
*/
|
||||
static async submitApplication(data: ApplicationFormData): Promise<{
|
||||
success: boolean;
|
||||
applicationId?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const application = await territoryManagerAPI.submitApplication(data);
|
||||
return {
|
||||
success: true,
|
||||
applicationId: application.id
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to submit application'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async getApplications(status?: string): Promise<TerritoryApplication[]> {
|
||||
try {
|
||||
return await territoryManagerAPI.getApplications(status);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static async reviewApplication(id: string, action: 'approve' | 'reject', notes?: string): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
await territoryManagerAPI.reviewApplication(id, action, notes);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to review application'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lead management
|
||||
*/
|
||||
static async loadLeads(): Promise<Lead[]> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) return [];
|
||||
|
||||
return await territoryManagerAPI.getLeads(territoryManager.id);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static async createLead(data: LeadFormData): Promise<{
|
||||
success: boolean;
|
||||
lead?: Lead;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const lead = await territoryManagerAPI.createLead(data);
|
||||
return {
|
||||
success: true,
|
||||
lead
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create lead'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async updateLead(id: string, data: Partial<Lead>): Promise<{
|
||||
success: boolean;
|
||||
lead?: Lead;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const lead = await territoryManagerAPI.updateLead(id, data);
|
||||
return {
|
||||
success: true,
|
||||
lead
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update lead'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteLead(id: string): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
await territoryManagerAPI.deleteLead(id);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete lead'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commission management
|
||||
*/
|
||||
static async loadCommissions(): Promise<Commission[]> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) return [];
|
||||
|
||||
return await territoryManagerAPI.getCommissions(territoryManager.id);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expense management
|
||||
*/
|
||||
static async submitExpenseReport(data: ExpenseReportFormData): Promise<{
|
||||
success: boolean;
|
||||
expenseReport?: ExpenseReport;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const expenseReport = await territoryManagerAPI.submitExpenseReport(data);
|
||||
return {
|
||||
success: true,
|
||||
expenseReport
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to submit expense report'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async loadExpenseReports(): Promise<ExpenseReport[]> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) return [];
|
||||
|
||||
return await territoryManagerAPI.getExpenseReports(territoryManager.id);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Referral management
|
||||
*/
|
||||
static async generateReferralLink(eventType?: string): Promise<{
|
||||
success: boolean;
|
||||
referralLink?: ReferralLink;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authenticated as territory manager'
|
||||
};
|
||||
}
|
||||
|
||||
const referralLink = await territoryManagerAPI.generateReferralLink(territoryManager.id, eventType);
|
||||
return {
|
||||
success: true,
|
||||
referralLink
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to generate referral link'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async loadReferralLinks(): Promise<ReferralLink[]> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) return [];
|
||||
|
||||
return await territoryManagerAPI.getReferralLinks(territoryManager.id);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Training and achievements
|
||||
*/
|
||||
static async loadAchievements(): Promise<Achievement[]> {
|
||||
try {
|
||||
return await territoryManagerAPI.getAchievements();
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static async loadTrainingProgress(): Promise<any[]> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) return [];
|
||||
|
||||
return await territoryManagerAPI.getTrainingProgress(territoryManager.id);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static async updateTrainingProgress(moduleId: string, score?: number): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authenticated as territory manager'
|
||||
};
|
||||
}
|
||||
|
||||
await territoryManagerAPI.updateTrainingProgress(territoryManager.id, moduleId, score);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update training progress'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
*/
|
||||
static async loadNotifications(): Promise<TMNotification[]> {
|
||||
try {
|
||||
const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();
|
||||
if (!territoryManager) return [];
|
||||
|
||||
return await territoryManagerAPI.getNotifications(territoryManager.id);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static async markNotificationAsRead(id: string): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
await territoryManagerAPI.markNotificationAsRead(id);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to mark notification as read'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async markAllNotificationsAsRead(): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const notifications = await this.loadNotifications();
|
||||
const unreadNotifications = notifications.filter(n => !n.read);
|
||||
|
||||
await Promise.all(
|
||||
unreadNotifications.map(n => territoryManagerAPI.markNotificationAsRead(n.id))
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to mark all notifications as read'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaderboard
|
||||
*/
|
||||
static async loadLeaderboard(period: 'monthly' | 'quarterly' | 'yearly' = 'monthly'): Promise<LeaderboardEntry[]> {
|
||||
try {
|
||||
return await territoryManagerAPI.getLeaderboard(period);
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marketing materials
|
||||
*/
|
||||
static async loadMarketingMaterials(): Promise<MarketingMaterial[]> {
|
||||
try {
|
||||
return await territoryManagerAPI.getMarketingMaterials();
|
||||
} catch (error) {
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility methods
|
||||
*/
|
||||
static formatCurrency(amount: number): string {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD'
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
static formatDate(dateString: string): string {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
static formatDateTime(dateString: string): string {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
static getStatusColor(status: string): string {
|
||||
const colors = {
|
||||
'pending': 'bg-yellow-600 text-white',
|
||||
'active': 'bg-green-600 text-white',
|
||||
'suspended': 'bg-red-600 text-white',
|
||||
'inactive': 'bg-gray-600 text-white',
|
||||
'cold': 'bg-gray-600 text-white',
|
||||
'contacted': 'bg-blue-600 text-white',
|
||||
'confirmed': 'bg-green-600 text-white',
|
||||
'converted': 'bg-purple-600 text-white',
|
||||
'dead': 'bg-red-600 text-white'
|
||||
};
|
||||
return colors[status] || 'bg-gray-600 text-white';
|
||||
}
|
||||
|
||||
/**
|
||||
* Private utility methods
|
||||
*/
|
||||
private static generateEarningsHistory(commissions: Commission[]): any[] {
|
||||
const monthlyEarnings = new Map<string, number>();
|
||||
|
||||
commissions.forEach(commission => {
|
||||
const month = commission.created_at.substring(0, 7); // YYYY-MM
|
||||
const current = monthlyEarnings.get(month) || 0;
|
||||
monthlyEarnings.set(month, current + commission.total_commission);
|
||||
});
|
||||
|
||||
return Array.from(monthlyEarnings.entries())
|
||||
.map(([month, amount]) => ({
|
||||
date: month + '-01',
|
||||
amount
|
||||
}))
|
||||
.sort((a, b) => a.date.localeCompare(b.date));
|
||||
}
|
||||
}
|
||||
|
||||
// Export the router for global use
|
||||
export const territoryManagerRouter = TerritoryManagerRouter;
|
||||
Reference in New Issue
Block a user