124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
'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,
|
|
};
|
|
}
|