Fix organization setup flow: redirect to onboarding for incomplete setup
This commit is contained in:
300
contexts/UserOrganizationsContext.tsx
Normal file
300
contexts/UserOrganizationsContext.tsx
Normal file
@@ -0,0 +1,300 @@
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user