Refine auth mechanics

This commit is contained in:
Sam Becker 2025-04-27 21:31:05 -05:00
parent 01c8b5d22b
commit 2326a0ef8d
5 changed files with 46 additions and 38 deletions

View File

@ -10,14 +10,19 @@ import { usePathname } from 'next/navigation';
import { PATH_ADMIN_PHOTOS, isPathAdmin, isPathSignIn } from './paths';
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
import { signOutAction } from '@/auth/actions';
import Spinner from '@/components/Spinner';
import AnimateItems from '@/components/AnimateItems';
import { useAppState } from '@/state/AppState';
import Spinner from '@/components/Spinner';
export default function Footer() {
const pathname = usePathname();
const { userEmail, clearAuthStateAndRedirectIfNecessary } = useAppState();
const {
userEmail,
userEmailEager,
isCheckingAuth,
clearAuthStateAndRedirectIfNecessary,
} = useAppState();
const showFooter = !isPathSignIn(pathname);
@ -38,31 +43,25 @@ export default function Footer() {
'text-dim min-h-10',
)}>
<div className="flex gap-x-3 xs:gap-x-4 grow flex-wrap">
{isPathAdmin(pathname)
{userEmail || userEmailEager
? <>
{userEmail === undefined &&
<Spinner size={14} className="translate-y-[2px]" />}
{userEmail && <>
<div className={clsx(
'truncate max-w-full',
)}>
{userEmail}
</div>
<form action={() => signOutAction()
.then(clearAuthStateAndRedirectIfNecessary)}>
<SubmitButtonWithStatus styleAs="link">
Sign out
</SubmitButtonWithStatus>
</form>
</>}
<div className="truncate max-w-full">
{userEmail || userEmailEager}
</div>
<form action={() => signOutAction()
.then(clearAuthStateAndRedirectIfNecessary)}>
<SubmitButtonWithStatus styleAs="link">
Sign out
</SubmitButtonWithStatus>
</form>
</>
: <>
<Link href={PATH_ADMIN_PHOTOS}>
Admin
</Link>
{SHOW_REPO_LINK &&
<RepoLink />}
</>}
: isCheckingAuth
? <Spinner size={16} className="translate-y-[2px]" />
: SHOW_REPO_LINK
? <RepoLink />
: <Link href={PATH_ADMIN_PHOTOS}>
Admin
</Link>}
</div>
<div className="flex items-center h-10">
<ThemeSwitcher />

View File

@ -13,12 +13,15 @@ const KEY_AUTH_EMAIL = 'authjs.email';
export const storeAuthEmailCookie = (email: string) =>
storeCookie(KEY_AUTH_EMAIL, email);
export const clearAuthEmailCookie = () =>
deleteCookie(KEY_AUTH_EMAIL);
export const getAuthEmailCookie = () =>
getCookie(KEY_AUTH_EMAIL);
export const hasAuthEmailCookie = () =>
Boolean(getCookie(KEY_AUTH_EMAIL));
export const clearAuthEmailCookie = () =>
deleteCookie(KEY_AUTH_EMAIL);
export const isCredentialsSignInError = (error: any) =>
(error.message || `${error}`).includes(KEY_CREDENTIALS_SIGN_IN_ERROR);

View File

@ -39,6 +39,7 @@ export type AppStateContextType = {
setRecipeModalProps?: Dispatch<SetStateAction<RecipeProps | undefined>>
// AUTH
userEmail?: string
userEmailEager?: string
setUserEmail?: Dispatch<SetStateAction<string | undefined>>
isUserSignedIn?: boolean
isUserSignedInEager?: boolean

View File

@ -18,11 +18,11 @@ import { AdminData, getAdminDataAction } from '@/admin/actions';
import {
storeAuthEmailCookie,
clearAuthEmailCookie,
hasAuthEmailCookie,
isCredentialsSignInError,
getAuthEmailCookie,
} from '@/auth';
import { useRouter, usePathname } from 'next/navigation';
import { isPathAdmin, PATH_ROOT } from '@/app/paths';
import { isPathProtected, PATH_ROOT } from '@/app/paths';
import { INITIAL_UPLOAD_STATE, UploadState } from '@/admin/upload';
import { RecipeProps } from '@/recipe';
import { getCountsForCategoriesCachedAction } from '@/category/actions';
@ -75,8 +75,8 @@ export default function AppStateProvider({
// AUTH
const [userEmail, setUserEmail] =
useState<string>();
const [isUserSignedInEager, setIsUserSignedInEager] =
useState(false);
const [userEmailEager, setUserEmailEager] =
useState<string>();
// ADMIN
const [adminUpdateTimes, setAdminUpdateTimes] =
useState<Date[]>([]);
@ -121,12 +121,12 @@ export default function AppStateProvider({
isLoading: isCheckingAuth,
} = useSWR('getAuth', getAuthAction);
useEffect(() => {
setIsUserSignedInEager(hasAuthEmailCookie());
setUserEmailEager(getAuthEmailCookie());
}, []);
useEffect(() => {
if (authError) {
setIsUserSignedInEager(false);
setUserEmail(undefined);
setUserEmailEager(undefined);
if (isCredentialsSignInError(authError)) {
clearAuthEmailCookie();
}
@ -135,6 +135,7 @@ export default function AppStateProvider({
}
}, [auth, authError]);
const isUserSignedIn = Boolean(userEmail);
const isUserSignedInEager = Boolean(userEmailEager);
const {
data: adminData,
@ -166,9 +167,9 @@ export default function AppStateProvider({
const clearAuthStateAndRedirectIfNecessary = useCallback(() => {
setUserEmail(undefined);
setIsUserSignedInEager(false);
setUserEmailEager(undefined);
clearAuthEmailCookie();
if (isPathAdmin(pathname)) { router.push(PATH_ROOT); }
if (isPathProtected(pathname)) { router.push(PATH_ROOT); }
}, [router, pathname]);
// Returns false when upload is cancelled
@ -216,6 +217,7 @@ export default function AppStateProvider({
// AUTH
isCheckingAuth,
userEmail,
userEmailEager,
setUserEmail,
isUserSignedIn,
isUserSignedInEager,

View File

@ -1,11 +1,14 @@
const DEFAULT_PATH = '/';
export const storeCookie = (
name: string,
value: string,
path= '/',
path = DEFAULT_PATH,
maxAge = 63158400,
sameSite = 'Lax',
) => {
if (typeof document !== 'undefined') {
console.log('storeCookie', name, value);
document.cookie =
`${name}=${value};Path=${path};Max-Age=${maxAge};SameSite=${sameSite}`;
}
@ -22,8 +25,8 @@ export const getCookie = (name: string) => {
}
};
export const deleteCookie = (name: string) => {
export const deleteCookie = (name: string, path = DEFAULT_PATH) => {
if (typeof document !== 'undefined') {
document.cookie = `${name}=;Max-Age=0`;
document.cookie = `${name}=;Path=${path};Max-Age=0`;
}
};