updates on the ui changes and changed into doormile
This commit is contained in:
@@ -1,14 +1,26 @@
|
||||
import React, { createContext, useState, useContext, useEffect } from 'react';
|
||||
import { base44 } from '@/api/base44Client';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
DOORMILE_TOKEN_KEY,
|
||||
DOORMILE_USER_KEY,
|
||||
loginAdmin,
|
||||
logoutAdmin,
|
||||
readStoredToken,
|
||||
readStoredUser,
|
||||
} from '@/api/doormile';
|
||||
|
||||
/**
|
||||
* Auth context.
|
||||
* Auth for the Doormile Express console.
|
||||
*
|
||||
* The reference app resolves an identity provider before rendering. The demo
|
||||
* has no provider, so it resolves the seeded user immediately — but it keeps
|
||||
* the same context shape (`isLoadingAuth`, `isLoadingPublicSettings`,
|
||||
* `authError`, `navigateToLogin`, …) so `App.jsx`, `ProtectedRoute` and every
|
||||
* consumer stay unchanged.
|
||||
* `POST /admin/login` returns a JWT and the admin record, and every subsequent
|
||||
* request carries that token — so the session lives entirely in localStorage
|
||||
* and there is nothing to fetch on boot. The provider still resolves
|
||||
* asynchronously (`isLoadingAuth`) rather than reading storage inline, because
|
||||
* rendering a route tree before the token is known flashes the login page at an
|
||||
* already-signed-in operator on every refresh.
|
||||
*
|
||||
* The axios client clears the same two keys and hard-navigates to `/login` on a
|
||||
* 401, so an expired token ends the session the same way signing out does — a
|
||||
* shell that renders but 401s on every fetch is worse than being sent back.
|
||||
*/
|
||||
|
||||
const AuthContext = createContext(/** @type {any} */ (null));
|
||||
@@ -19,55 +31,86 @@ export const AuthProvider = ({ children }) => {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [isLoadingAuth, setIsLoadingAuth] = useState(true);
|
||||
const [authChecked, setAuthChecked] = useState(false);
|
||||
const [authError, setAuthError] = useState(null);
|
||||
|
||||
const checkUserAuth = async () => {
|
||||
const checkUserAuth = useCallback(() => {
|
||||
setIsLoadingAuth(true);
|
||||
try {
|
||||
const currentUser = await base44.auth.me();
|
||||
setUser(currentUser);
|
||||
setIsAuthenticated(true);
|
||||
} catch {
|
||||
setIsAuthenticated(false);
|
||||
} finally {
|
||||
setIsLoadingAuth(false);
|
||||
setAuthChecked(true);
|
||||
}
|
||||
};
|
||||
const token = readStoredToken();
|
||||
const stored = readStoredUser();
|
||||
setIsAuthenticated(Boolean(token));
|
||||
setUser(token ? stored : null);
|
||||
setIsLoadingAuth(false);
|
||||
setAuthChecked(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkUserAuth();
|
||||
}, [checkUserAuth]);
|
||||
|
||||
/* A sign-out in another tab has to end the session in this one too —
|
||||
otherwise this tab keeps a rendered console around a token that is gone. */
|
||||
useEffect(() => {
|
||||
const onStorage = (event) => {
|
||||
if (event.key === DOORMILE_TOKEN_KEY || event.key === DOORMILE_USER_KEY) checkUserAuth();
|
||||
};
|
||||
window.addEventListener('storage', onStorage);
|
||||
return () => window.removeEventListener('storage', onStorage);
|
||||
}, [checkUserAuth]);
|
||||
|
||||
/**
|
||||
* Signs in. Resolves to the API's own `{ success, message }` rather than
|
||||
* throwing on a refused credential, so the login form can show the server's
|
||||
* wording instead of a generic failure.
|
||||
*/
|
||||
const login = useCallback(async (email, password) => {
|
||||
setAuthError(null);
|
||||
try {
|
||||
const result = await loginAdmin(email, password);
|
||||
if (result?.success) {
|
||||
setUser(result.user ?? readStoredUser());
|
||||
setIsAuthenticated(true);
|
||||
return { success: true };
|
||||
}
|
||||
const message = result?.message || 'Those credentials were not accepted';
|
||||
setAuthError(message);
|
||||
return { success: false, message };
|
||||
} catch (err) {
|
||||
const message = err?.message || 'Could not reach the Doormile API';
|
||||
setAuthError(message);
|
||||
return { success: false, message };
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = (shouldRedirect = true) => {
|
||||
const logout = useCallback((shouldRedirect = true) => {
|
||||
logoutAdmin();
|
||||
setUser(null);
|
||||
setIsAuthenticated(false);
|
||||
base44.auth.logout(shouldRedirect ? '/' : undefined);
|
||||
};
|
||||
if (shouldRedirect) window.location.replace('/login');
|
||||
}, []);
|
||||
|
||||
const navigateToLogin = () => {
|
||||
base44.auth.redirectToLogin();
|
||||
};
|
||||
const navigateToLogin = useCallback(() => window.location.replace('/login'), []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
user,
|
||||
isAuthenticated,
|
||||
isLoadingAuth,
|
||||
// No remote app settings to fetch in the demo.
|
||||
isLoadingPublicSettings: false,
|
||||
authError: null,
|
||||
appPublicSettings: null,
|
||||
authChecked,
|
||||
logout,
|
||||
navigateToLogin,
|
||||
checkUserAuth,
|
||||
checkAppState: checkUserAuth,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
user,
|
||||
isAuthenticated,
|
||||
isLoadingAuth,
|
||||
/* No remote app settings on this backend — kept so consumers that gate on
|
||||
it stay unchanged. */
|
||||
isLoadingPublicSettings: false,
|
||||
appPublicSettings: null,
|
||||
authError,
|
||||
authChecked,
|
||||
login,
|
||||
logout,
|
||||
navigateToLogin,
|
||||
checkUserAuth,
|
||||
checkAppState: checkUserAuth,
|
||||
}),
|
||||
[user, isAuthenticated, isLoadingAuth, authError, authChecked, login, logout, navigateToLogin, checkUserAuth]
|
||||
);
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
|
||||
Reference in New Issue
Block a user