Refine new user auth experience
This commit is contained in:
parent
11af89065b
commit
eaecfae7c9
@ -1,36 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import PhotoUploadWithStatus from '@/photo/PhotoUploadWithStatus';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import Link from 'next/link';
|
||||
import { FaArrowRight } from 'react-icons/fa';
|
||||
|
||||
export default function AdminCTA({
|
||||
shouldResize,
|
||||
onLastUpload,
|
||||
}: {
|
||||
shouldResize: boolean
|
||||
onLastUpload: () => Promise<void>
|
||||
}) {
|
||||
const { isUserSignedIn } = useAppState();
|
||||
|
||||
return (
|
||||
<div className="flex justify-center pt-4">
|
||||
{isUserSignedIn
|
||||
? <PhotoUploadWithStatus
|
||||
inputId="admin-cta"
|
||||
shouldResize={shouldResize}
|
||||
onLastUpload={onLastUpload}
|
||||
showStatusText={false}
|
||||
/>
|
||||
: <Link
|
||||
href={PATH_ADMIN_PHOTOS}
|
||||
className="button primary"
|
||||
>
|
||||
<span>Admin Dashboard</span>
|
||||
<FaArrowRight size={10} />
|
||||
</Link>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
src/admin/SignInOrUploadClient.tsx
Normal file
45
src/admin/SignInOrUploadClient.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import SignInForm from '@/auth/SignInForm';
|
||||
import clsx from 'clsx';
|
||||
import PhotoUploadWithStatus from '@/photo/PhotoUploadWithStatus';
|
||||
|
||||
export default function SignInOrUploadClient({
|
||||
shouldResize,
|
||||
onLastUpload,
|
||||
}: {
|
||||
shouldResize: boolean
|
||||
onLastUpload: () => Promise<void>
|
||||
}) {
|
||||
const { isUserSignedIn, isCheckingAuth } = useAppState();
|
||||
|
||||
return (
|
||||
<div className={clsx(
|
||||
'flex justify-center items-center flex-col gap-4',
|
||||
)}>
|
||||
<div>
|
||||
{isCheckingAuth
|
||||
? 'Loading ...'
|
||||
: isUserSignedIn
|
||||
? 'Add your first photo'
|
||||
: 'Sign in to upload photos'}
|
||||
</div>
|
||||
{!isCheckingAuth && isUserSignedIn === false &&
|
||||
<div className="flex justify-center my-2 sm:my-4">
|
||||
<SignInForm
|
||||
className="max-w-[90%] sm:max-w-none"
|
||||
includeTitle={false}
|
||||
shouldRedirect={false}
|
||||
/>
|
||||
</div>}
|
||||
{isUserSignedIn === true &&
|
||||
<PhotoUploadWithStatus
|
||||
inputId="admin-cta"
|
||||
shouldResize={shouldResize}
|
||||
onLastUpload={onLastUpload}
|
||||
showStatusText={false}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -11,13 +11,26 @@ import {
|
||||
} from 'react';
|
||||
import { getAuthAction, signInAction } from './actions';
|
||||
import ErrorNote from '@/components/ErrorNote';
|
||||
import { KEY_CALLBACK_URL, KEY_CREDENTIALS_SIGN_IN_ERROR } from '.';
|
||||
import {
|
||||
KEY_CALLBACK_URL,
|
||||
KEY_CREDENTIALS_SIGN_IN_ERROR,
|
||||
KEY_CREDENTIALS_SUCCESS,
|
||||
} from '.';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { FiLock } from 'react-icons/fi';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
|
||||
export default function SignInForm() {
|
||||
export default function SignInForm({
|
||||
includeTitle = true,
|
||||
shouldRedirect = true,
|
||||
className,
|
||||
}: {
|
||||
includeTitle?: boolean
|
||||
shouldRedirect?: boolean
|
||||
className?: string
|
||||
}) {
|
||||
const params = useSearchParams();
|
||||
|
||||
const { setUserEmail } = useAppState();
|
||||
@ -33,12 +46,15 @@ export default function SignInForm() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (response === KEY_CREDENTIALS_SUCCESS) {
|
||||
setUserEmail?.(email);
|
||||
}
|
||||
return () => {
|
||||
// Capture user email before unmounting
|
||||
getAuthAction().then(auth =>
|
||||
setUserEmail?.(auth?.user?.email ?? undefined));
|
||||
};
|
||||
}, [setUserEmail]);
|
||||
}, [setUserEmail, response, email]);
|
||||
|
||||
const isFormValid =
|
||||
email.length > 0 &&
|
||||
@ -48,7 +64,9 @@ export default function SignInForm() {
|
||||
<Container className={clsx(
|
||||
'w-[calc(100vw-1.5rem)] sm:w-[min(360px,90vw)]',
|
||||
'px-6 py-5',
|
||||
className,
|
||||
)}>
|
||||
{includeTitle &&
|
||||
<h1 className={clsx(
|
||||
'flex gap-3 items-center justify-center',
|
||||
'self-start text-2xl mb-3.5',
|
||||
@ -57,11 +75,8 @@ export default function SignInForm() {
|
||||
<span className="text-main">
|
||||
Sign in
|
||||
</span>
|
||||
</h1>
|
||||
<form
|
||||
action={action}
|
||||
className="w-full"
|
||||
>
|
||||
</h1>}
|
||||
<form action={action} className="w-full">
|
||||
<div className="space-y-6 w-full -translate-y-0.5">
|
||||
{response === KEY_CREDENTIALS_SIGN_IN_ERROR &&
|
||||
<ErrorNote>
|
||||
@ -83,11 +98,12 @@ export default function SignInForm() {
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
/>
|
||||
{shouldRedirect &&
|
||||
<input
|
||||
type="hidden"
|
||||
name={KEY_CALLBACK_URL}
|
||||
value={params.get(KEY_CALLBACK_URL) ?? ''}
|
||||
/>
|
||||
value={params.get(KEY_CALLBACK_URL) || PATH_ADMIN_PHOTOS}
|
||||
/>}
|
||||
</div>
|
||||
<SubmitButtonWithStatus disabled={!isFormValid}>
|
||||
Sign in
|
||||
|
||||
@ -5,12 +5,12 @@ import {
|
||||
KEY_CREDENTIALS_CALLBACK_ROUTE_ERROR_URL,
|
||||
KEY_CREDENTIALS_SIGN_IN_ERROR,
|
||||
KEY_CREDENTIALS_SIGN_IN_ERROR_URL,
|
||||
KEY_CREDENTIALS_SUCCESS,
|
||||
auth,
|
||||
generateAuthSecret,
|
||||
signIn,
|
||||
signOut,
|
||||
} from '@/auth';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import type { Session } from 'next-auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
@ -38,7 +38,10 @@ export const signInAction = async (
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
redirect(formData.get(KEY_CALLBACK_URL) as string || PATH_ADMIN_PHOTOS);
|
||||
if (formData.get(KEY_CALLBACK_URL)) {
|
||||
redirect(formData.get(KEY_CALLBACK_URL) as string);
|
||||
}
|
||||
return KEY_CREDENTIALS_SUCCESS;
|
||||
};
|
||||
|
||||
export const signOutAction = async () =>
|
||||
|
||||
@ -7,6 +7,7 @@ export const KEY_CREDENTIALS_SIGN_IN_ERROR_URL =
|
||||
'https://errors.authjs.dev#credentialssignin';
|
||||
export const KEY_CREDENTIALS_CALLBACK_ROUTE_ERROR_URL =
|
||||
'https://errors.authjs.dev#callbackrouteerror';
|
||||
export const KEY_CREDENTIALS_SUCCESS = 'success';
|
||||
export const KEY_CALLBACK_URL = 'callbackUrl';
|
||||
|
||||
export const {
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import AdminCTA from '@/admin/AdminCTA';
|
||||
import Container from '@/components/Container';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { IS_SITE_READY, PRESERVE_ORIGINAL_UPLOADS } from '@/app/config';
|
||||
import { PATH_ADMIN_CONFIGURATION } from '@/app/paths';
|
||||
import AdminAppConfiguration from '@/admin/AdminAppConfiguration';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import Link from 'next/link';
|
||||
import { HiOutlinePhotograph } from 'react-icons/hi';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import SignInOrUploadClient from '@/admin/SignInOrUploadClient';
|
||||
import Link from 'next/link';
|
||||
import { PATH_ADMIN_CONFIGURATION } from '@/app/paths';
|
||||
|
||||
export default function PhotosEmptyState() {
|
||||
return (
|
||||
@ -30,11 +30,7 @@ export default function PhotosEmptyState() {
|
||||
{!IS_SITE_READY
|
||||
? <AdminAppConfiguration simplifiedView />
|
||||
: <div className="max-w-md text-center space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
Add your first photo:
|
||||
</div>
|
||||
<AdminCTA
|
||||
<SignInOrUploadClient
|
||||
shouldResize={!PRESERVE_ORIGINAL_UPLOADS}
|
||||
onLastUpload={async () => {
|
||||
'use server';
|
||||
@ -42,7 +38,6 @@ export default function PhotosEmptyState() {
|
||||
revalidatePath('/admin', 'layout');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
Change the name of this blog and other configuration
|
||||
by editing environment variables referenced in
|
||||
|
||||
@ -44,6 +44,7 @@ export type AppStateContext = {
|
||||
isUserSignedInEager?: boolean
|
||||
clearAuthStateAndRedirect?: () => void
|
||||
// ADMIN
|
||||
isCheckingAuth?: boolean
|
||||
adminUpdateTimes?: Date[]
|
||||
registerAdminUpdate?: () => void
|
||||
refreshAdminData?: () => void
|
||||
|
||||
@ -91,7 +91,11 @@ export default function AppStateProvider({
|
||||
|
||||
const invalidateSwr = useCallback(() => setSwrTimestamp(Date.now()), []);
|
||||
|
||||
const { data: auth, error: authError } = useSWR('getAuth', getAuthAction);
|
||||
const {
|
||||
data: auth,
|
||||
error: authError,
|
||||
isLoading: isCheckingAuth,
|
||||
} = useSWR('getAuth', getAuthAction);
|
||||
useEffect(() => {
|
||||
setIsUserSignedInEager(hasAuthEmailCookie());
|
||||
if (!authError) {
|
||||
@ -176,6 +180,7 @@ export default function AppStateProvider({
|
||||
recipeModalProps,
|
||||
setRecipeModalProps,
|
||||
// AUTH
|
||||
isCheckingAuth,
|
||||
userEmail,
|
||||
setUserEmail,
|
||||
isUserSignedIn,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user