Files
auditly/contexts/UserOrganizationsContext.tsx

301 lines
11 KiB
TypeScript

import React, { createContext, useContext, useEffect, useState } from 'react';
import { useAuth } from './AuthContext';
import { isFirebaseConfigured } from '../services/firebase';
import { API_URL } from '../constants';
import { demoStorage } from '../services/demoStorage';
interface UserOrganization {
orgId: string;
name: string;
role: 'owner' | 'admin' | 'employee';
onboardingCompleted: boolean;
joinedAt: number;
}
interface UserOrganizationsContextType {
organizations: UserOrganization[];
selectedOrgId: string | null;
loading: boolean;
selectOrganization: (orgId: string) => void;
createOrganization: (name: string) => Promise<{ orgId: string; requiresSubscription?: boolean }>;
joinOrganization: (inviteCode: string) => Promise<string>;
refreshOrganizations: () => Promise<void>;
createCheckoutSession: (orgId: string, userEmail: string) => Promise<{ sessionUrl: string; sessionId: string }>;
getSubscriptionStatus: (orgId: string) => Promise<any>;
}
const UserOrganizationsContext = createContext<UserOrganizationsContextType | undefined>(undefined);
export const UserOrganizationsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user } = useAuth();
const [organizations, setOrganizations] = useState<UserOrganization[]>([]);
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
// Load user's organizations
const loadOrganizations = async () => {
if (!user) {
setOrganizations([]);
setLoading(false);
return;
}
try {
if (!isFirebaseConfigured) {
// Demo mode - fetch from server API
const response = await fetch(`${API_URL}/api/user/${user.uid}/organizations`);
if (response.ok) {
const data = await response.json();
setOrganizations(data.organizations || []);
} else {
console.error('Failed to load organizations:', response.status);
setOrganizations([]);
}
} else {
// Firebase mode - fetch from Cloud Functions
const response = await fetch(`${API_URL}/getUserOrganizations?userId=${user.uid}`);
if (response.ok) {
const data = await response.json();
setOrganizations(data.organizations || []);
} else {
console.error('Failed to load organizations:', response.status);
setOrganizations([]);
}
}
} catch (error) {
console.error('Failed to load organizations:', error);
setOrganizations([]);
} finally {
setLoading(false);
}
};
// Initialize selected org from session storage
useEffect(() => {
const savedOrgId = sessionStorage.getItem('auditly_selected_org');
if (savedOrgId) {
setSelectedOrgId(savedOrgId);
}
}, []);
// Load organizations when user changes
useEffect(() => {
loadOrganizations();
}, [user]);
const selectOrganization = (orgId: string) => {
setSelectedOrgId(orgId);
sessionStorage.setItem('auditly_selected_org', orgId);
};
const createOrganization = async (name: string): Promise<{ orgId: string; requiresSubscription?: boolean }> => {
if (!user) throw new Error('User not authenticated');
try {
let newOrg: UserOrganization;
let requiresSubscription = false;
if (!isFirebaseConfigured) {
// Demo mode - use server API
const response = await fetch(`${API_URL}/api/organizations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, userId: user.uid })
});
if (!response.ok) {
throw new Error(`Failed to create organization: ${response.status}`);
}
const data = await response.json();
newOrg = {
orgId: data.orgId,
name: data.name,
role: data.role,
onboardingCompleted: data.onboardingCompleted,
joinedAt: data.joinedAt
};
setOrganizations(prev => [...prev, newOrg]);
} else {
// Firebase mode - use Cloud Function
const response = await fetch(`${API_URL}/createOrganization`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, userId: user.uid })
});
if (!response.ok) {
throw new Error(`Failed to create organization: ${response.status}`);
}
const data = await response.json();
newOrg = {
orgId: data.orgId,
name: data.name,
role: data.role,
onboardingCompleted: data.onboardingCompleted,
joinedAt: data.joinedAt
};
requiresSubscription = data.requiresSubscription || false;
setOrganizations(prev => [...prev, newOrg]);
}
return { orgId: newOrg.orgId, requiresSubscription };
} catch (error) {
console.error('Failed to create organization:', error);
throw error;
}
};
const joinOrganization = async (inviteCode: string): Promise<string> => {
if (!user) throw new Error('User not authenticated');
try {
if (!isFirebaseConfigured) {
// Demo mode - use server API to get and consume invite
const inviteStatusRes = await fetch(`/api/invitations/${inviteCode}`);
if (!inviteStatusRes.ok) {
throw new Error('Invalid or expired invite code');
}
const inviteData = await inviteStatusRes.json();
if (inviteData.used) {
throw new Error('Invite code has already been used');
}
// Consume the invite
const consumeRes = await fetch(`/api/invitations/${inviteCode}/consume`, {
method: 'POST'
});
if (!consumeRes.ok) {
throw new Error('Failed to consume invite');
}
const consumedData = await consumeRes.json();
const orgId = consumedData.orgId;
// Get organization data (this might be from localStorage for demo mode)
const orgData = demoStorage.getOrganization(orgId);
if (!orgData) {
throw new Error('Organization not found');
}
const userOrg: UserOrganization = {
orgId: orgId,
name: orgData.name,
role: 'employee',
onboardingCompleted: orgData.onboardingCompleted || false,
joinedAt: Date.now()
};
setOrganizations(prev => [...prev, userOrg]);
return orgId;
} else {
// Firebase mode - use Cloud Function
const response = await fetch(`${API_URL}/joinOrganization`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: user.uid, inviteCode })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to join organization');
}
const data = await response.json();
const userOrg: UserOrganization = {
orgId: data.orgId,
name: data.name,
role: data.role,
onboardingCompleted: data.onboardingCompleted,
joinedAt: data.joinedAt
};
setOrganizations(prev => [...prev, userOrg]);
return data.orgId;
}
} catch (error) {
console.error('Failed to join organization:', error);
throw error;
}
};
const refreshOrganizations = async () => {
setLoading(true);
await loadOrganizations();
};
const createCheckoutSession = async (orgId: string, userEmail: string): Promise<{ sessionUrl: string; sessionId: string }> => {
if (!user) throw new Error('User not authenticated');
try {
const response = await fetch(`${API_URL}/createCheckoutSession`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orgId,
userId: user.uid,
userEmail
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to create checkout session');
}
const data = await response.json();
return {
sessionUrl: data.sessionUrl,
sessionId: data.sessionId
};
} catch (error) {
console.error('Failed to create checkout session:', error);
throw error;
}
};
const getSubscriptionStatus = async (orgId: string) => {
try {
const response = await fetch(`${API_URL}/getSubscriptionStatus?orgId=${orgId}`);
if (!response.ok) {
throw new Error('Failed to get subscription status');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to get subscription status:', error);
throw error;
}
};
return (
<UserOrganizationsContext.Provider value={{
organizations,
selectedOrgId,
loading,
selectOrganization,
createOrganization,
joinOrganization,
refreshOrganizations,
createCheckoutSession,
getSubscriptionStatus
}}>
{children}
</UserOrganizationsContext.Provider>
);
};
export const useUserOrganizations = () => {
const context = useContext(UserOrganizationsContext);
if (!context) {
throw new Error('useUserOrganizations must be used within UserOrganizationsProvider');
}
return context;
};