108 lines
3.3 KiB
JavaScript
108 lines
3.3 KiB
JavaScript
import React, { createContext, useState, useContext, useEffect } from 'react';
|
|
import { base44 } from '@/api/base44Client';
|
|
|
|
/**
|
|
* Auth context.
|
|
*
|
|
* The single source of truth for "is anyone signed in", and it is not a flag
|
|
* this app sets. `checkUserAuth` calls `GET /me`; the server reads its own
|
|
* session table and either returns a user or answers 401. Success means there
|
|
* is a session, failure means there is not, and there is no third answer this
|
|
* code can manufacture.
|
|
*
|
|
* The context shape is unchanged from the demo's version — `isLoadingAuth`,
|
|
* `isLoadingPublicSettings`, `authError`, `navigateToLogin` and the rest — so
|
|
* `App.jsx`, `ProtectedRoute` and every consumer stay as they were. What
|
|
* changed is where the answer comes from: a real session cookie rather than a
|
|
* client that reported the seeded user as permanently signed in.
|
|
*/
|
|
|
|
const AuthContext = createContext(/** @type {any} */ (null));
|
|
|
|
/** @param {any} props */
|
|
export const AuthProvider = ({ children }) => {
|
|
const [user, setUser] = useState(null);
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
const [isLoadingAuth, setIsLoadingAuth] = useState(true);
|
|
const [authChecked, setAuthChecked] = useState(false);
|
|
|
|
const checkUserAuth = async () => {
|
|
setIsLoadingAuth(true);
|
|
try {
|
|
const currentUser = await base44.auth.me();
|
|
setUser(currentUser);
|
|
setIsAuthenticated(true);
|
|
} catch {
|
|
// A 401 is the ordinary state of a signed-out visitor, and it is also
|
|
// what an expired session looks like on the first request after it
|
|
// lapses. Both mean the same thing here.
|
|
setUser(null);
|
|
setIsAuthenticated(false);
|
|
} finally {
|
|
setIsLoadingAuth(false);
|
|
setAuthChecked(true);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
checkUserAuth();
|
|
}, []);
|
|
|
|
/**
|
|
* Signs in.
|
|
*
|
|
* Resolves to the user on success and throws the API's error otherwise. The
|
|
* error's message is already the generic one the server chose — it does not
|
|
* say whether the address exists — so callers show it as-is rather than
|
|
* writing their own.
|
|
*/
|
|
const login = async ({ email, password, rememberMe = false }) => {
|
|
const currentUser = await base44.auth.login({ email, password, rememberMe });
|
|
setUser(currentUser);
|
|
setIsAuthenticated(true);
|
|
setAuthChecked(true);
|
|
return currentUser;
|
|
};
|
|
|
|
const logout = async (redirectTo = '/admin/login') => {
|
|
setUser(null);
|
|
setIsAuthenticated(false);
|
|
// Revokes the session server-side, expires the cookie, then redirects.
|
|
await base44.auth.logout(redirectTo);
|
|
};
|
|
|
|
const navigateToLogin = () => {
|
|
base44.auth.redirectToLogin();
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
user,
|
|
isAuthenticated,
|
|
isLoadingAuth,
|
|
// No remote app settings to fetch in the demo.
|
|
isLoadingPublicSettings: false,
|
|
authError: null,
|
|
appPublicSettings: null,
|
|
authChecked,
|
|
login,
|
|
logout,
|
|
navigateToLogin,
|
|
checkUserAuth,
|
|
checkAppState: checkUserAuth,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useAuth = () => {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error('useAuth must be used within an AuthProvider');
|
|
}
|
|
return context;
|
|
};
|