ligun and sogup
This commit is contained in:
parent
b6633d6f25
commit
4573048a47
24 changed files with 482 additions and 226 deletions
|
@ -1,9 +1,10 @@
|
|||
import { useParams } from 'react-router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState, FormEvent, useCallback, Ref } from 'react'
|
||||
import SingleColumnLayout from '../layouts/SingleColumnLayout.tsx'
|
||||
import TextInput from '../components/TextInput.tsx'
|
||||
import PrimaryButton from '../components/PrimaryButton.tsx'
|
||||
import PrimaryLinkButton from '../components/PrimaryLinkButton.tsx'
|
||||
import { invalid, valid, Validation } from '../utils/validation.ts'
|
||||
|
||||
const SignupCodeKey = 'signupCode'
|
||||
|
||||
|
@ -12,9 +13,17 @@ export default function SignupPage() {
|
|||
const [signupCode, setSignupCode] = useState<string | null>(null)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const [username, setUsername] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [username, setUsername, usernameError, validateUsername] =
|
||||
useValidatedInput(isValidUsername)
|
||||
|
||||
const [email, setEmail, emailError, validateEmail] = useValidatedInput(isValidEmail)
|
||||
|
||||
const [password, setPassword, passwordError, validatePassword] =
|
||||
useValidatedInput(isValidPassword)
|
||||
|
||||
const userNameInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const emailInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const passwordInputRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const dialogRef = useRef<HTMLDialogElement | null>(null)
|
||||
|
||||
|
@ -35,8 +44,29 @@ export default function SignupPage() {
|
|||
}
|
||||
}, [code, signupCode])
|
||||
|
||||
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
const isUsernameValid = validateUsername()
|
||||
const isEmailValid = validateEmail()
|
||||
const isPasswordValid = validatePassword()
|
||||
|
||||
if (!isPasswordValid) {
|
||||
passwordInputRef.current?.focus()
|
||||
}
|
||||
|
||||
if (!isEmailValid) {
|
||||
emailInputRef.current?.focus()
|
||||
}
|
||||
|
||||
if (!isUsernameValid) {
|
||||
userNameInputRef.current?.focus()
|
||||
}
|
||||
|
||||
if (!isUsernameValid || !isEmailValid || !isPasswordValid) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsSubmitting(true)
|
||||
|
||||
try {
|
||||
|
@ -51,34 +81,33 @@ export default function SignupPage() {
|
|||
<main className="w-full mx-auto p-4">
|
||||
<div className="mt-12">
|
||||
<form className="flex flex-col gap-4 max-w-md" onSubmit={onSubmit}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="username" className="text-sm text-gray-600">
|
||||
username
|
||||
</label>
|
||||
<TextInput id="username" value={username} onInput={setUsername} required />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="email" className="text-sm text-gray-600">
|
||||
email (optional)
|
||||
</label>
|
||||
<TextInput id="email" value={email} onInput={setEmail} required />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="password" className="text-sm text-gray-600">
|
||||
password
|
||||
</label>
|
||||
<TextInput
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onInput={setPassword}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PrimaryButton className="mt-4" disabled={isSubmitting} type="submit">
|
||||
<FormInput
|
||||
id="username"
|
||||
value={username}
|
||||
onInput={setUsername}
|
||||
error={usernameError}
|
||||
ref={userNameInputRef}
|
||||
/>
|
||||
<FormInput
|
||||
id="email"
|
||||
value={email}
|
||||
onInput={setEmail}
|
||||
error={emailError}
|
||||
ref={emailInputRef}
|
||||
/>
|
||||
<FormInput
|
||||
id="password"
|
||||
value={password}
|
||||
onInput={setPassword}
|
||||
error={passwordError}
|
||||
type="password"
|
||||
ref={passwordInputRef}
|
||||
/>
|
||||
<PrimaryButton
|
||||
className="mt-4"
|
||||
disabled={isSubmitting || !!usernameError || !!passwordError}
|
||||
type="submit"
|
||||
>
|
||||
{isSubmitting ? 'wait...' : 'give me an account pls'}
|
||||
</PrimaryButton>
|
||||
</form>
|
||||
|
@ -109,3 +138,87 @@ export default function SignupPage() {
|
|||
</SingleColumnLayout>
|
||||
)
|
||||
}
|
||||
|
||||
interface FormInputProps {
|
||||
id: string
|
||||
value: string
|
||||
onInput: (value: string) => void
|
||||
error: string | null
|
||||
type?: 'text' | 'password'
|
||||
ref: Ref<HTMLInputElement>
|
||||
}
|
||||
|
||||
function FormInput({ id, value, onInput, error, type = 'text', ref }: FormInputProps) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label htmlFor={id} className="text-sm text-gray-600">
|
||||
{id}
|
||||
</label>
|
||||
<TextInput ref={ref} type={type} id={id} value={value} onInput={onInput} />
|
||||
<span className="text-xs h-3 text-red-500">{error}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type UseValidateInputReturn = [string, (value: string) => void, string | null, () => boolean]
|
||||
|
||||
function useValidatedInput(validator: (value: string) => Validation): UseValidateInputReturn {
|
||||
const [value, setValue] = useState<string>('')
|
||||
const [error, setError] = useState<string | null>(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 isValidEmail(email: string): Validation {
|
||||
if (!email) return valid()
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||
if (emailRegex.test(email)) {
|
||||
return valid()
|
||||
} else {
|
||||
return invalid("um sorry but that doesn't look like an email 🤔")
|
||||
}
|
||||
}
|
||||
|
||||
function isValidPassword(password: string): Validation {
|
||||
if (password.length >= 10) {
|
||||
return valid()
|
||||
} else {
|
||||
return invalid("that isn't a good password :/")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue