feat(phase-a): remove Neo4j from startup chain, clean auth flow

- Remove NeoUserProvider + NeoInstituteProvider from App.tsx startup chain
- Strip user_db_name/school_db_name from CCUser; add school_id (Phase B wires it to Supabase)
- Remove DatabaseNameService from AuthContext and UserContext
- Remove provisionUser() call from login path; API endpoint preserved for Phase B decision
- Simplify UserContext.resolveProfile: fast-path JWT metadata then background Supabase fetch
- Replace user.user_db_name reads in singlePlayerPage + snapshotService with null-safe guards
- Add useDeviceContext hook (desktop/tablet/phone/iwb, persists to localStorage)

App now loads to dashboard without any Neo4j dependency at startup.
Canvas opens to blank TLDraw state; Phase B rebuilds navigation on Supabase.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
2026-05-25 13:06:39 +00:00
co-authored by Claude Sonnet 4.6
parent 4139eb8fd3
commit 7b546c933e
8 changed files with 63 additions and 146 deletions
+1 -9
View File
@@ -4,7 +4,6 @@ import { Session, User } from '@supabase/supabase-js';
import { CCUser, CCUserMetadata, authService } from '../services/auth/authService';
import { logger } from '../debugConfig';
import { supabase } from '../supabaseClient';
import { DatabaseNameService } from '../services/graph/databaseNameService';
import { storageService, StorageKeys } from '../services/auth/localStorageService';
export interface AuthContextType {
@@ -52,20 +51,13 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const baseDisplayName = metadata.display_name || metadata.name || metadata.preferred_username || baseUsername;
const userType = (metadata.user_type || 'email_teacher').trim();
const storedUserDb = DatabaseNameService.getStoredUserDatabase();
const storedSchoolDb = DatabaseNameService.getStoredSchoolDatabase();
const userDbName = storedUserDb || DatabaseNameService.getUserPrivateDB(userType || 'standard', supabaseUser.id);
const schoolDbName = storedSchoolDb || '';
const resolvedUser: CCUser = {
id: supabaseUser.id,
email: supabaseUser.email,
user_type: userType,
username: baseUsername,
display_name: baseDisplayName,
user_db_name: userDbName,
school_db_name: schoolDbName,
school_id: null,
created_at: supabaseUser.created_at,
updated_at: supabaseUser.updated_at
};
+8 -95
View File
@@ -4,8 +4,6 @@ import { supabase } from '../supabaseClient';
import { logger } from '../debugConfig';
import { CCUser, CCUserMetadata } from '../services/auth/authService';
import { UserPreferences } from '../services/auth/profileService';
import { DatabaseNameService } from '../services/graph/databaseNameService';
import { provisionUser } from '../services/provisioningService';
import { storageService, StorageKeys } from '../services/auth/localStorageService';
export interface UserContextType {
@@ -112,33 +110,23 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
let profileRow: Record<string, unknown> | null = null;
// Fast-path: build profile from auth metadata + localStorage immediately.
// This clears the spinner before any network call so the page renders on refresh
// without waiting for the Supabase profiles query (~200ms).
// Fast-path: build profile from auth metadata immediately (no spinner on refresh).
const fastMetadata = userInfo.user_metadata as CCUserMetadata;
const fastStoredUserDb = DatabaseNameService.getStoredUserDatabase();
const fastStoredSchoolDb = DatabaseNameService.getStoredSchoolDatabase();
const fastUserDb = fastStoredUserDb || DatabaseNameService.getUserPrivateDB(fastMetadata?.user_type || '', userInfo.id);
const fastProfile: CCUser = {
id: userInfo.id,
email: userInfo.email,
user_type: fastMetadata?.user_type || '',
username: fastMetadata?.username || userInfo.email?.split('@')[0] || 'user',
display_name: String(fastMetadata?.display_name || ''),
user_db_name: String(fastUserDb || ''),
school_db_name: String(fastStoredSchoolDb || ''),
school_id: null,
created_at: userInfo.created_at,
updated_at: userInfo.updated_at
};
DatabaseNameService.rememberDatabaseNames({
userDbName: fastProfile.user_db_name,
schoolDbName: fastProfile.school_db_name
});
if (mountedRef.current && !isInitialized) {
setProfile(fastProfile);
setLoading(false);
setIsInitialized(true);
logger.debug('user-context', '⚡ Fast-path: profile initialized from auth metadata, no spinner');
logger.debug('user-context', '⚡ Fast-path: profile initialized from auth metadata');
}
// Background: query Supabase profiles for authoritative data (user_db_name, school_db_name, display_name).
@@ -172,80 +160,15 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
hasSchoolDb: !!profileRow?.school_db_name
});
const metadata = userInfo.user_metadata as CCUserMetadata;
logger.debug('user-context', '🔧 Step 7: Processing user metadata...', {
hasMetadata: !!metadata,
userType: metadata?.user_type
});
let userDbName = profileRow?.user_db_name ?? null;
let schoolDbName = profileRow?.school_db_name ?? null;
const storedUserDb = DatabaseNameService.getStoredUserDatabase();
const storedSchoolDb = DatabaseNameService.getStoredSchoolDatabase();
// Start provisioning in background (non-blocking)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const provisioningPromise = provisionUser(userInfo.id, authSession?.access_token ?? null)
.then(provisioned => {
if (provisioned) {
logger.debug('user-context', '✅ Provisioning completed in background', {
userDbName: provisioned.user_db_name,
workerDbName: provisioned.worker_db_name
});
// Update localStorage with provisioned values
DatabaseNameService.rememberDatabaseNames({
userDbName: provisioned.user_db_name,
schoolDbName: provisioned.worker_db_name || ''
});
}
})
.catch(provisionError => {
logger.warn('user-context', '⚠️ Background provisioning failed', {
userId: userInfo?.id,
provisionError: provisionError instanceof Error ? provisionError.message : String(provisionError)
});
});
if (!userDbName && storedUserDb) {
userDbName = storedUserDb;
}
if (!schoolDbName && storedSchoolDb) {
schoolDbName = storedSchoolDb;
}
logger.debug('user-context', '️ Database name resolution', {
userDbName,
schoolDbName
});
if (!userDbName) {
userDbName = DatabaseNameService.getUserPrivateDB(metadata.user_type || '', userInfo.id);
}
if (!schoolDbName) {
schoolDbName = '';
}
DatabaseNameService.rememberDatabaseNames({
userDbName: String(userDbName || ''),
schoolDbName: String(schoolDbName || '')
});
logger.debug('user-context', '🔧 Creating user profile object...', {
userId: userInfo.id,
userDbName,
schoolDbName,
userType: metadata.user_type
});
const userProfile: CCUser = {
id: userInfo.id,
email: userInfo.email,
user_type: metadata.user_type || '',
username: metadata.username || '',
display_name: String(metadata.display_name || ''),
user_db_name: String(userDbName || ''),
school_db_name: String(schoolDbName || ''),
school_id: (profileRow?.school_id as string) ?? null,
created_at: userInfo.created_at,
updated_at: userInfo.updated_at
};
@@ -268,9 +191,7 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
logger.debug('user-context', '✅ User profile loaded', {
userId: userProfile.id,
userType: userProfile.user_type,
username: userProfile.username,
userDbName: userProfile.user_db_name,
schoolDbName: userProfile.school_db_name
username: userProfile.username
});
setError(null);
} catch (error) {
@@ -295,22 +216,14 @@ export const UserProvider = ({ children }: { children: React.ReactNode }) => {
user_type: metadata?.user_type || 'email_teacher',
username: metadata?.username || userInfo.email?.split('@')[0] || 'user',
display_name: metadata?.display_name || userInfo.email?.split('@')[0] || 'User',
user_db_name: DatabaseNameService.getUserPrivateDB(metadata?.user_type || 'email_teacher', userInfo.id),
school_db_name: '',
school_id: null,
created_at: userInfo.created_at,
updated_at: userInfo.updated_at
};
DatabaseNameService.rememberDatabaseNames({
userDbName: fallbackProfile.user_db_name,
schoolDbName: fallbackProfile.school_db_name
});
setProfile(fallbackProfile);
logger.debug('user-context', '✅ Fallback profile created', {
userId: fallbackProfile.id,
userType: fallbackProfile.user_type,
userDbName: fallbackProfile.user_db_name
userType: fallbackProfile.user_type
});
} else {
setProfile(null);