first commit
This commit is contained in:
123
src/features/auth/hooks/useJoinForm.ts
Normal file
123
src/features/auth/hooks/useJoinForm.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
'use client';
|
||||
|
||||
import {useCallback, useState} from 'react';
|
||||
import {useRouter, useSearchParams} from 'next/navigation';
|
||||
import {useSession} from '@/features/auth/providers/SessionProvider';
|
||||
import {authRepository} from '@/features/auth/repositories/authRepository';
|
||||
import {destinationForUser} from '@/features/auth/services/roleDestination';
|
||||
import type {InvitationPreview} from '@/features/auth/types/join';
|
||||
|
||||
/** The platform's own floor, checked here too so the common mistake costs no round trip. */
|
||||
export const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* Redeeming an invitation, in two steps.
|
||||
*
|
||||
* 1. The code is checked BEFORE anybody chooses a password, so the screen
|
||||
* can say what they are joining and a mistyped code is caught first.
|
||||
* 2. Name and password are sent with the code; the answer is a full session
|
||||
* and the person goes straight into the console — never to a login form.
|
||||
*
|
||||
* A code in the link (`/join?code=…`) is prefilled, so a code sent over
|
||||
* WhatsApp is one tap. Dashes and case in a code do not matter upstream; it is
|
||||
* sent exactly as typed.
|
||||
*/
|
||||
export function useJoinForm() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const {refresh} = useSession();
|
||||
|
||||
const [code, setCode] = useState(() => searchParams.get('code') ?? '');
|
||||
const [preview, setPreview] = useState<InvitationPreview | null>(null);
|
||||
const [fullName, setFullName] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirm, setConfirm] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const check = useCallback(
|
||||
async (value: string) => {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
setError('Enter the invitation code you were given.');
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
const res = await authRepository.invitation(trimmed);
|
||||
setIsSubmitting(false);
|
||||
if (!res.ok || !res.data) {
|
||||
setError(res.message ?? 'That invitation code is not valid. Ask for a new one.');
|
||||
return;
|
||||
}
|
||||
setPreview(res.data);
|
||||
// Prefilled from the invitation, and still editable: it is how they
|
||||
// appear to colleagues, and whoever invited them may have guessed.
|
||||
setFullName((prev) => prev || res.data!.fullName);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const register = useCallback(async () => {
|
||||
if (password.length < MIN_PASSWORD_LENGTH) {
|
||||
setError(`Choose a password of at least ${MIN_PASSWORD_LENGTH} characters.`);
|
||||
return;
|
||||
}
|
||||
if (password !== confirm) {
|
||||
setError('The two passwords do not match.');
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
const res = await authRepository.register({
|
||||
code: code.trim(),
|
||||
fullName: fullName.trim(),
|
||||
password,
|
||||
});
|
||||
if (!res.ok || !res.data) {
|
||||
setIsSubmitting(false);
|
||||
setError(res.message ?? 'Could not create your account. Please try again.');
|
||||
return;
|
||||
}
|
||||
// The cookies are set; let the provider read them before navigating, so
|
||||
// the workspace does not paint its signed-out state first. isSubmitting
|
||||
// stays true until the navigation commits, as on the sign-in form.
|
||||
await refresh();
|
||||
router.replace(destinationForUser(res.data.user));
|
||||
router.refresh();
|
||||
}, [code, fullName, password, confirm, refresh, router]);
|
||||
|
||||
const submit = useCallback(
|
||||
(event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (isSubmitting) return;
|
||||
void (preview ? register() : check(code));
|
||||
},
|
||||
[isSubmitting, preview, register, check, code],
|
||||
);
|
||||
|
||||
/** Back to step one, for somebody holding the wrong code. */
|
||||
const reset = useCallback(() => {
|
||||
setPreview(null);
|
||||
setPassword('');
|
||||
setConfirm('');
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
code,
|
||||
preview,
|
||||
fullName,
|
||||
password,
|
||||
confirm,
|
||||
error,
|
||||
isSubmitting,
|
||||
setCode,
|
||||
setFullName,
|
||||
setPassword,
|
||||
setConfirm,
|
||||
check,
|
||||
submit,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user