Merge branch 'dev' into dev-kartik

This commit is contained in:
Kartik Datrika
2026-03-24 15:41:25 +05:30
55 changed files with 3413 additions and 168 deletions

View File

@@ -51,6 +51,7 @@ const loadPersistedUser = (): User | null => {
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// eslint-disable-next-line react-refresh/only-export-components
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (context === undefined) {
@@ -96,10 +97,21 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
}, []);
const logout = useCallback(() => {
// Notify sidecar to unlock Redis session + Ozonetel logout
const token = localStorage.getItem("helix_access_token");
if (token) {
const apiUrl = import.meta.env.VITE_API_URL ?? "http://localhost:4100";
fetch(`${apiUrl}/auth/logout`, {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
}).catch(() => {});
}
setUser(DEFAULT_USER);
setIsAuthenticated(false);
localStorage.removeItem("helix_access_token");
localStorage.removeItem("helix_refresh_token");
localStorage.removeItem("helix_agent_config");
localStorage.removeItem(STORAGE_KEY);
}, []);
@@ -118,4 +130,5 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
);
};
// eslint-disable-next-line react-refresh/only-export-components
export { getInitials };

View File

@@ -33,6 +33,7 @@ type DataContextType = {
const DataContext = createContext<DataContextType | undefined>(undefined);
// eslint-disable-next-line react-refresh/only-export-components
export const useData = (): DataContextType => {
const context = useContext(DataContext);
@@ -76,12 +77,19 @@ export const DataProvider = ({ children }: DataProviderProps) => {
const gql = <T,>(query: string) => apiClient.graphql<T>(query, undefined, { silent: true }).catch(() => null);
const [leadsData, campaignsData, adsData, followUpsData, activitiesData, callsData, patientsData] = await Promise.all([
// eslint-disable-next-line @typescript-eslint/no-explicit-any
gql<any>(LEADS_QUERY),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
gql<any>(CAMPAIGNS_QUERY),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
gql<any>(ADS_QUERY),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
gql<any>(FOLLOW_UPS_QUERY),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
gql<any>(LEAD_ACTIVITIES_QUERY),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
gql<any>(CALLS_QUERY),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
gql<any>(PATIENTS_QUERY),
]);
@@ -92,6 +100,7 @@ export const DataProvider = ({ children }: DataProviderProps) => {
if (activitiesData) setLeadActivities(transformLeadActivities(activitiesData));
if (callsData) setCalls(transformCalls(callsData));
if (patientsData) setPatients(transformPatients(patientsData));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
setError(err.message ?? "Failed to load data");
} finally {

View File

@@ -13,12 +13,29 @@ import {
} from "@/state/sip-state";
import type { SIPConfig } from "@/types/sip";
const DEFAULT_CONFIG: SIPConfig = {
displayName: import.meta.env.VITE_SIP_DISPLAY_NAME ?? "Helix Agent",
uri: import.meta.env.VITE_SIP_URI ?? "",
password: import.meta.env.VITE_SIP_PASSWORD ?? "",
wsServer: import.meta.env.VITE_SIP_WS_SERVER ?? "",
stunServers: "stun:stun.l.google.com:19302",
const getSipConfig = (): SIPConfig => {
try {
const stored = localStorage.getItem("helix_agent_config");
if (stored) {
const config = JSON.parse(stored);
return {
displayName: "Helix Agent",
uri: config.sipUri,
password: config.sipPassword,
wsServer: config.sipWsServer,
stunServers: "stun:stun.l.google.com:19302",
};
}
} catch {
/* intentional */
}
return {
displayName: import.meta.env.VITE_SIP_DISPLAY_NAME ?? "Helix Agent",
uri: import.meta.env.VITE_SIP_URI ?? "",
password: import.meta.env.VITE_SIP_PASSWORD ?? "",
wsServer: import.meta.env.VITE_SIP_WS_SERVER ?? "",
stunServers: "stun:stun.l.google.com:19302",
};
};
export const SipProvider = ({ children }: PropsWithChildren) => {
@@ -41,7 +58,7 @@ export const SipProvider = ({ children }: PropsWithChildren) => {
// Auto-connect SIP on mount
useEffect(() => {
connectSip(DEFAULT_CONFIG);
connectSip(getSipConfig());
}, []);
// Call duration timer
@@ -81,6 +98,7 @@ export const SipProvider = ({ children }: PropsWithChildren) => {
};
// Hook for components to access SIP actions + state
// eslint-disable-next-line react-refresh/only-export-components
export const useSip = () => {
const [connectionStatus] = useAtom(sipConnectionStatusAtom);
const [callState] = useAtom(sipCallStateAtom);
@@ -132,7 +150,7 @@ export const useSip = () => {
isInCall: ["ringing-in", "ringing-out", "active"].includes(callState),
ozonetelStatus: "logged-in" as const,
ozonetelError: null as string | null,
connect: () => connectSip(DEFAULT_CONFIG),
connect: () => connectSip(getSipConfig()),
disconnect: disconnectSip,
makeCall,
answer,

View File

@@ -10,6 +10,7 @@ interface ThemeContextType {
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// eslint-disable-next-line react-refresh/only-export-components
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
@@ -76,6 +77,7 @@ export const ThemeProvider = ({ children, defaultTheme = "system", storageKey =
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [theme]);
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;