initial commit

This commit is contained in:
ray
2026-07-12 10:01:39 -04:00
commit fc8290d668
185 changed files with 38831 additions and 0 deletions
@@ -0,0 +1,95 @@
import { createContext, useContext, useEffect, useState, useCallback, type ReactNode } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { pb } from '../../lib/pocketbase';
interface ImpersonatedTechInfo {
id: string;
email: string;
displayName?: string;
}
interface TechnicianContextValue {
effectiveUserId: string;
isImpersonating: boolean;
impersonatedTech: ImpersonatedTechInfo | null;
stopImpersonation: () => void;
}
const TechnicianContext = createContext<TechnicianContextValue>({
effectiveUserId: '',
isImpersonating: false,
impersonatedTech: null,
stopImpersonation: () => {},
});
export function TechnicianProvider({ children }: { children: ReactNode }) {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const viewAs = searchParams.get('view_as');
const authUserId = pb.authStore.model?.id as string | undefined;
const [impersonatedTech, setImpersonatedTech] = useState<ImpersonatedTechInfo | null>(null);
const isImpersonating = !!viewAs && viewAs !== authUserId;
const effectiveUserId = isImpersonating ? viewAs! : (authUserId || '');
useEffect(() => {
if (isImpersonating && viewAs) {
pb.collection('users').getOne(viewAs).then((user) => {
setImpersonatedTech({
id: user.id,
email: user.email as string,
displayName: user.displayName as string | undefined,
});
}).catch(() => {
setImpersonatedTech(null);
});
} else {
setImpersonatedTech(null);
}
}, [isImpersonating, viewAs]);
const stopImpersonation = useCallback(() => {
navigate('/team');
}, [navigate]);
return (
<TechnicianContext.Provider
value={{
effectiveUserId,
isImpersonating,
impersonatedTech,
stopImpersonation,
}}
>
{children}
</TechnicianContext.Provider>
);
}
export function useTechnicianContext(): TechnicianContextValue {
return useContext(TechnicianContext);
}
export function useTechnicianNav() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const viewAs = searchParams.get('view_as');
const buildTechUrl = useCallback(
(path: string): string => {
if (!viewAs) return path;
const separator = path.includes('?') ? '&' : '?';
return `${path}${separator}view_as=${encodeURIComponent(viewAs)}`;
},
[viewAs],
);
const navigateTech = useCallback(
(path: string, opts?: { replace?: boolean; state?: unknown }) => {
navigate(buildTechUrl(path), opts);
},
[navigate, buildTechUrl],
);
return { buildTechUrl, navigateTech };
}