update ui update and fix layout issue
This commit is contained in:
114
src/features/auth/hooks/useLoginForm.ts
Normal file
114
src/features/auth/hooks/useLoginForm.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
'use client';
|
||||
|
||||
import {useCallback, useState} from 'react';
|
||||
import {useRouter, useSearchParams} from 'next/navigation';
|
||||
import {useToast} from '@astryxdesign/core/Toast';
|
||||
import {useSession} from '@/features/auth/providers/SessionProvider';
|
||||
import {resolveRedirectTarget} from '@/features/auth/services/redirectTarget';
|
||||
import type {LoginError} from '@/features/auth/types/auth';
|
||||
|
||||
/**
|
||||
* Every behaviour of the sign-in form, with no markup attached.
|
||||
*
|
||||
* Pulled out of the component for the reason the brief asks for: the form was
|
||||
* a 314-line file where a fake `setTimeout` login sat in the middle of layout
|
||||
* markup. Now the view renders state and the rules live here — which also
|
||||
* makes the flow testable without mounting a page, and means a redesign of the
|
||||
* screen cannot silently change what "signed in" means.
|
||||
*
|
||||
* The chain below this hook is: SessionProvider → authService (validation and
|
||||
* domain rules) → authRepository (transport) → POST /api/auth/login. This hook
|
||||
* knows about none of it beyond the first link.
|
||||
*/
|
||||
|
||||
export interface LoginFormState {
|
||||
email: string;
|
||||
password: string;
|
||||
rememberMe: boolean;
|
||||
/** Field-addressed, so a message renders under the input that caused it. */
|
||||
errors: Partial<Record<LoginError['field'], string>>;
|
||||
isSubmitting: boolean;
|
||||
}
|
||||
|
||||
export function useLoginForm() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const toast = useToast();
|
||||
const {login} = useSession();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
const [errors, setErrors] = useState<LoginFormState['errors']>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Shared with GuestGuard, which redirects on the same event — see
|
||||
// resolveRedirectTarget for why that matters and how `next` is validated.
|
||||
const destination = resolveRedirectTarget(searchParams.get('next'));
|
||||
|
||||
const submit = useCallback(
|
||||
async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (isSubmitting) return;
|
||||
|
||||
setErrors({});
|
||||
setIsSubmitting(true);
|
||||
|
||||
const result = await login({email, password, rememberMe});
|
||||
|
||||
if (!result.ok) {
|
||||
setErrors({[result.error.field]: result.error.message});
|
||||
// The toast carries what the inline message cannot: it is announced,
|
||||
// it survives a field the user has scrolled past, and it distinguishes
|
||||
// "we rejected your credentials" from "we never reached the server".
|
||||
toast({type: 'error', body: result.error.message});
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Deliberately NOT clearing isSubmitting on success: the button stays in
|
||||
// its loading state until the navigation commits, so the form cannot be
|
||||
// submitted twice while the route transition is in flight.
|
||||
router.replace(destination);
|
||||
// The session cookie changed, so any server-rendered layout above this
|
||||
// route is stale. Without this, the workspace can paint its signed-out
|
||||
// seed until something else happens to refetch.
|
||||
router.refresh();
|
||||
},
|
||||
[
|
||||
isSubmitting,
|
||||
login,
|
||||
email,
|
||||
password,
|
||||
rememberMe,
|
||||
toast,
|
||||
router,
|
||||
destination,
|
||||
],
|
||||
);
|
||||
|
||||
/** Clearing on edit: an error that outlives the typo it describes is noise. */
|
||||
const updateEmail = useCallback((value: string) => {
|
||||
setEmail(value);
|
||||
setErrors((prev) => (prev.email ? {...prev, email: undefined} : prev));
|
||||
}, []);
|
||||
|
||||
const updatePassword = useCallback((value: string) => {
|
||||
setPassword(value);
|
||||
setErrors((prev) =>
|
||||
prev.password ? {...prev, password: undefined} : prev,
|
||||
);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
email,
|
||||
password,
|
||||
rememberMe,
|
||||
errors,
|
||||
isSubmitting,
|
||||
setEmail: updateEmail,
|
||||
setPassword: updatePassword,
|
||||
setRememberMe,
|
||||
submit,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user