import { useNavigate, useParams } from 'react-router-dom' import { useEffect, useRef, useState, FormEvent, useCallback, Ref } from 'react' import SingleColumnLayout from '../../../layouts/SingleColumnLayout.tsx' import TextInput from '../../../components/inputs/TextInput.tsx' import Button from '../../../components/buttons/Button.tsx' import { invalid, valid, Validation } from '../../../utils/validation.ts' import { AuthService } from '../authService.ts' import LinkButton from '../../../components/buttons/LinkButton.tsx' import NavBar from '../../../components/NavBar.tsx' import NavButton from '../../../components/buttons/NavButton.tsx' import { useTranslations } from '../../i18n/translations.ts' const SignupCodeKey = 'signupCode' interface SignupPageProps { authService: AuthService } export default function SignupPage({ authService }: SignupPageProps) { const { t } = useTranslations() const { code } = useParams() const [signupCode, setSignupCode] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) const [rememberMe, setRememberMe] = useState(false) const [error, setError] = useState('') const [username, setUsername, usernameError, validateUsername] = useValidatedInput(isValidUsername) const [password, setPassword, passwordError, validatePassword] = useValidatedInput(isValidPassword) const userNameInputRef = useRef(null) const passwordInputRef = useRef(null) const navigate = useNavigate() useEffect(() => { if (signupCode) return let theSignupCode: string | null if (code) { theSignupCode = code setSignupCode(theSignupCode) localStorage.setItem(SignupCodeKey, theSignupCode) } else { theSignupCode = localStorage.getItem(SignupCodeKey) setSignupCode(theSignupCode) } }, [code, signupCode]) useEffect(() => {}, [signupCode]) const onSubmit = async (e: FormEvent) => { e.preventDefault() if (!signupCode) { throw new Error("there's no code") } const isUsernameValid = validateUsername() const isPasswordValid = validatePassword() if (!isPasswordValid) { passwordInputRef.current?.focus() } if (!isUsernameValid) { userNameInputRef.current?.focus() } if (!isUsernameValid || !isPasswordValid) { return } setIsSubmitting(true) try { await authService.signup(username, password, signupCode, rememberMe) navigate('/') } catch (e: unknown) { const err = e as Error setError(err.message) } finally { setIsSubmitting(false) } } return ( {t('nav.home')} } >
setRememberMe(e.target.checked)} className="h-4 w-4" />
{t('auth.register.login_instead')} {error}
) } interface FormInputProps { id: string label: string value: string onInput: (value: string) => void error: string | null type?: 'text' | 'password' ref: Ref } function FormInput({ id, label, value, onInput, error, type = 'text', ref }: FormInputProps) { return (
{error}
) } type UseValidateInputReturn = [string, (value: string) => void, string | null, () => boolean] function useValidatedInput(validator: (value: string) => Validation): UseValidateInputReturn { const [value, setValue] = useState('') const [error, setError] = useState(null) const [keepValidating, setKeepValidating] = useState(false) const validate = useCallback(() => { const { isValid, error } = validator(value) if (isValid) { setError(null) } else { // We only want to validate on input after they have invalidly submitted once. // It's annoying if we set error messages before they've even finished typing. setKeepValidating(true) setError(error) } return isValid }, [validator, value]) useEffect(() => { if (keepValidating) { validate() } }, [keepValidating, validate]) return [value, setValue, error, validate] } function isValidUsername(username: string): Validation { if (!username) return invalid('you need to enter a username :/') if (username.length < 3) { return invalid('not long enough :(') } const usernameRegex = /^[a-zA-Z0-9_-]+$/ if (usernameRegex.test(username)) { return valid() } else { return invalid("that's not a good username :'(") } } function isValidPassword(password: string): Validation { if (password.length >= 6) { return valid() } else { return invalid("that isn't a good password :/") } }