- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
162 lines
4.0 KiB
TypeScript
162 lines
4.0 KiB
TypeScript
import { createContext, createSignal, useContext, onMount, Accessor, JSX } from 'solid-js';
|
|
import { getClerk, loadClerk, getClerkUrls } from './clerk-client';
|
|
import { User, UserRole, AuthState } from './types';
|
|
|
|
type ClerkUser = any;
|
|
|
|
interface ClerkSession {
|
|
getId: () => string;
|
|
getUser: () => ClerkUser;
|
|
}
|
|
|
|
interface ClerkClient {
|
|
user: () => ClerkUser | null;
|
|
session: () => ClerkSession | null;
|
|
isLoading: boolean;
|
|
signOut: () => Promise<void>;
|
|
}
|
|
|
|
const AuthContext = createContext<Accessor<AuthState> | undefined>(undefined);
|
|
const AuthActionsContext = createContext<{
|
|
signIn: () => void;
|
|
signOut: () => Promise<void>;
|
|
updateUser: (data: Partial<User>) => Promise<void>;
|
|
clerkClient: Accessor<ClerkClient | null>;
|
|
} | undefined>(undefined);
|
|
|
|
export { AuthContext, AuthActionsContext };
|
|
|
|
function clerkUserToUser(clerkUser: ClerkUser): User {
|
|
const primaryEmail = clerkUser.primaryEmailAddress?.emailAddress || '';
|
|
const firstName = clerkUser.firstName || '';
|
|
const lastName = clerkUser.lastName || '';
|
|
const name = [firstName, lastName].filter(Boolean).join(' ') || primaryEmail.split('@')[0] || 'User';
|
|
|
|
return {
|
|
id: clerkUser.id,
|
|
email: primaryEmail,
|
|
name,
|
|
avatarUrl: clerkUser.imageUrl,
|
|
role: 'owner' as UserRole,
|
|
};
|
|
}
|
|
|
|
export function ClerkProvider(props: { children: JSX.Element }) {
|
|
const [state, setState] = createSignal<AuthState>({
|
|
user: null,
|
|
isLoading: true,
|
|
isAuthenticated: false,
|
|
error: null,
|
|
});
|
|
|
|
const [clerkClient, setClerkClient] = createSignal<ClerkClient | null>(null);
|
|
|
|
onMount(async () => {
|
|
try {
|
|
const client = await loadClerk();
|
|
if (!client) {
|
|
setState({
|
|
user: null,
|
|
isLoading: false,
|
|
isAuthenticated: false,
|
|
error: 'Authentication service unavailable',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const wrappedClient: ClerkClient = {
|
|
user: () => client.user,
|
|
session: () => (client.session as any) || null,
|
|
isLoading: false,
|
|
signOut: async () => {
|
|
await client.signOut();
|
|
setState({
|
|
user: null,
|
|
isLoading: false,
|
|
isAuthenticated: false,
|
|
error: null,
|
|
});
|
|
},
|
|
};
|
|
|
|
setClerkClient(wrappedClient);
|
|
|
|
if (client.user) {
|
|
setState({
|
|
user: clerkUserToUser(client.user),
|
|
isLoading: false,
|
|
isAuthenticated: true,
|
|
error: null,
|
|
});
|
|
} else {
|
|
setState((prev) => ({ ...prev, isLoading: false }));
|
|
}
|
|
} catch (err) {
|
|
setState({
|
|
user: null,
|
|
isLoading: false,
|
|
isAuthenticated: false,
|
|
error: err instanceof Error ? err.message : 'Failed to initialize auth',
|
|
});
|
|
}
|
|
});
|
|
|
|
const signIn = () => {
|
|
const urls = getClerkUrls();
|
|
window.location.href = urls.signInUrl;
|
|
};
|
|
|
|
const signOut = async () => {
|
|
const client = getClerk();
|
|
if (client) {
|
|
await client.signOut();
|
|
}
|
|
setState({
|
|
user: null,
|
|
isLoading: false,
|
|
isAuthenticated: false,
|
|
error: null,
|
|
});
|
|
};
|
|
|
|
const updateUser = async (data: Partial<User>) => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
user: prev.user ? { ...prev.user, ...data } : null,
|
|
}));
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={state}>
|
|
<AuthActionsContext.Provider value={{ signIn, signOut, updateUser, clerkClient }}>
|
|
{props.children}
|
|
</AuthActionsContext.Provider>
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth(): Accessor<AuthState> {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error('useAuth must be used within a ClerkProvider');
|
|
}
|
|
return context;
|
|
}
|
|
|
|
export function useAuthActions() {
|
|
const context = useContext(AuthActionsContext);
|
|
if (!context) {
|
|
throw new Error('useAuthActions must be used within a ClerkProvider');
|
|
}
|
|
return context;
|
|
}
|
|
|
|
export function requireAuth() {
|
|
const auth = useAuth();
|
|
const authState = auth();
|
|
if (!authState.isAuthenticated) {
|
|
throw new Error('Authentication required');
|
|
}
|
|
return authState.user!;
|
|
}
|