164 lines
4.9 KiB
TypeScript
164 lines
4.9 KiB
TypeScript
import React, { createContext, useContext, useEffect, useState } from "react";
|
|
import { SetSnackType } from '../components/Snack';
|
|
import { User, Guest, Candidate } from 'types/types';
|
|
import { ApiClient } from "services/api-client";
|
|
import { debugConversion } from "types/conversion";
|
|
|
|
type UserContextType = {
|
|
apiClient: ApiClient;
|
|
user: User | null;
|
|
guest: Guest;
|
|
candidate: Candidate | null;
|
|
setUser: (user: User | null) => void;
|
|
setCandidate: (candidate: Candidate | null) => void;
|
|
};
|
|
|
|
const UserContext = createContext<UserContextType | undefined>(undefined);
|
|
|
|
const useUser = () => {
|
|
const ctx = useContext(UserContext);
|
|
if (!ctx) throw new Error("useUser must be used within a UserProvider");
|
|
return ctx;
|
|
};
|
|
|
|
interface UserProviderProps {
|
|
children: React.ReactNode;
|
|
setSnack: SetSnackType;
|
|
};
|
|
|
|
const UserProvider: React.FC<UserProviderProps> = (props: UserProviderProps) => {
|
|
const { children, setSnack } = props;
|
|
const [apiClient, setApiClient] = useState<ApiClient>(new ApiClient());
|
|
const [candidate, setCandidate] = useState<Candidate | null>(null);
|
|
const [guest, setGuest] = useState<Guest | null>(null);
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [activeUser, setActiveUser] = useState<User | null>(null);
|
|
|
|
useEffect(() => {
|
|
console.log("Candidate =>", candidate);
|
|
}, [candidate]);
|
|
|
|
useEffect(() => {
|
|
console.log("Guest =>", guest);
|
|
}, [guest]);
|
|
|
|
/* If the user changes to a non-null value, create a new
|
|
* apiClient with the access token */
|
|
useEffect(() => {
|
|
console.log("User => ", user);
|
|
if (user === null) {
|
|
return;
|
|
}
|
|
/* This apiClient will persist until the user is changed
|
|
* or logged out */
|
|
const accessToken = localStorage.getItem('accessToken');
|
|
if (!accessToken) {
|
|
throw Error("accessToken is not set for user!");
|
|
}
|
|
setApiClient(new ApiClient(accessToken));
|
|
}, [user]);
|
|
|
|
/* Handle logout if any consumers of UserProvider setUser to NULL */
|
|
useEffect(() => {
|
|
/* If there is an active user and it is the same as the
|
|
* new user, do nothing */
|
|
if (activeUser && activeUser.email === user?.email) {
|
|
return;
|
|
}
|
|
|
|
const logout = async () => {
|
|
if (!activeUser) {
|
|
return;
|
|
}
|
|
console.log(`Logging out ${activeUser.email}`);
|
|
try {
|
|
const accessToken = localStorage.getItem('accessToken');
|
|
const refreshToken = localStorage.getItem('refreshToken');
|
|
if (!accessToken || !refreshToken) {
|
|
setSnack("Authentication tokens are invalid.", "error");
|
|
return;
|
|
}
|
|
const results = await apiClient.logout(accessToken, refreshToken);
|
|
if (results.error) {
|
|
console.error(results.error);
|
|
setSnack(results.error.message, "error")
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
setSnack(`Unable to logout: ${e}`, "error")
|
|
}
|
|
|
|
localStorage.removeItem('accessToken');
|
|
localStorage.removeItem('refreshToken');
|
|
localStorage.removeItem('userData');
|
|
createGuestSession();
|
|
setUser(null);
|
|
};
|
|
|
|
setActiveUser(user);
|
|
if (!user) {
|
|
logout();
|
|
}
|
|
}, [user, apiClient, activeUser]);
|
|
|
|
const createGuestSession = () => {
|
|
console.log("TODO: Convert this to query the server for the session instead of generating it.");
|
|
const sessionId = `guest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
const guest: Guest = {
|
|
sessionId,
|
|
createdAt: new Date(),
|
|
lastActivity: new Date(),
|
|
ipAddress: 'unknown',
|
|
userAgent: navigator.userAgent
|
|
};
|
|
setGuest(guest);
|
|
debugConversion(guest, 'Guest Session');
|
|
};
|
|
|
|
const checkExistingAuth = () => {
|
|
const accessToken = localStorage.getItem('accessToken');
|
|
const userData = localStorage.getItem('userData');
|
|
if (accessToken && userData) {
|
|
try {
|
|
const user = JSON.parse(userData);
|
|
// Convert dates back to Date objects if they're stored as strings
|
|
if (user.createdAt && typeof user.createdAt === 'string') {
|
|
user.createdAt = new Date(user.createdAt);
|
|
}
|
|
if (user.updatedAt && typeof user.updatedAt === 'string') {
|
|
user.updatedAt = new Date(user.updatedAt);
|
|
}
|
|
if (user.lastLogin && typeof user.lastLogin === 'string') {
|
|
user.lastLogin = new Date(user.lastLogin);
|
|
}
|
|
setApiClient(new ApiClient(accessToken));
|
|
setUser(user);
|
|
} catch (e) {
|
|
localStorage.removeItem('accessToken');
|
|
localStorage.removeItem('refreshToken');
|
|
localStorage.removeItem('userData');
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create guest session on component mount
|
|
useEffect(() => {
|
|
createGuestSession();
|
|
checkExistingAuth();
|
|
}, []);
|
|
|
|
if (guest === null) {
|
|
return <></>;
|
|
}
|
|
|
|
return (
|
|
<UserContext.Provider value={{ apiClient, candidate, setCandidate, user, setUser, guest }}>
|
|
{children}
|
|
</UserContext.Provider>
|
|
);
|
|
};
|
|
|
|
export {
|
|
UserProvider,
|
|
useUser
|
|
}; |