Basic error validation in the form

This commit is contained in:
Manuel Bustillo 2024-12-07 13:32:08 +01:00
parent d085a2ab19
commit 4d576b07da
4 changed files with 43 additions and 9 deletions

View File

@ -1,7 +1,7 @@
/* Copyright (C) 2024 Manuel Bustillo*/ /* Copyright (C) 2024 Manuel Bustillo*/
import { getCsrfToken, getSlug } from '@/app/lib/utils'; import { asArray, getCsrfToken, getSlug } from '@/app/lib/utils';
import { Captcha, User } from '@/app/lib/definitions'; import { Captcha, StructuredErrors, User } from '@/app/lib/definitions';
export function login({ email, password, onLogin }: { email: string, password: string, onLogin: (user: User) => void }) { export function login({ email, password, onLogin }: { email: string, password: string, onLogin: (user: User) => void }) {
return fetch(`/api/${getSlug()}/users/sign_in`, { return fetch(`/api/${getSlug()}/users/sign_in`, {
@ -34,13 +34,23 @@ export function logout({ onLogout }: { onLogout: () => void }) {
.catch((error) => console.error(error)); .catch((error) => console.error(error));
} }
export function register({slug, email, password, passwordConfirmation, captcha, onRegister}: { function flattenErrors(errors: StructuredErrors): string[] {
if(errors instanceof Array) {
return errors;
}
return Object.keys(errors).map((key) => {
return `${key}: ${asArray(errors[key]).join(', ')}`;
});
}
export function register({slug, email, password, passwordConfirmation, captcha, onRegister, onError}: {
slug: string, slug: string,
email: string, email: string,
password: string, password: string,
passwordConfirmation: string, passwordConfirmation: string,
captcha: Captcha, captcha: Captcha,
onRegister: () => void onRegister: () => void,
onError: (errors: string[]) => void
}){ }){
fetch(`/api/${slug}/users`, { fetch(`/api/${slug}/users`, {
method: 'POST', method: 'POST',
@ -55,6 +65,13 @@ export function register({slug, email, password, passwordConfirmation, captcha,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-TOKEN': getCsrfToken(), 'X-CSRF-TOKEN': getCsrfToken(),
} }
}).then(onRegister) }).then((response) => {
.catch((error) => console.error(error)); if(response.ok) {
response.json().then(onRegister);
} else {
response.json().then((data: any) => {
onError(data.errors && flattenErrors(data.errors) || [data.error]);
});
}
})
} }

View File

@ -70,3 +70,7 @@ export type Captcha = {
id: string; id: string;
answer: string; answer: string;
} }
export type StructuredErrors = {
[key: string]: string[]|string;
};

View File

@ -13,3 +13,8 @@ export const getSlug = () => localStorage.getItem('slug') || 'default';
export const capitalize = (val:string) => { export const capitalize = (val:string) => {
return String(val).charAt(0).toUpperCase() + String(val).slice(1); return String(val).charAt(0).toUpperCase() + String(val).slice(1);
} }
// From https://stackoverflow.com/a/62118163/3607039
export function asArray<T>(value: T | T[]): T[] {
return ([] as T[]).concat(value)
}

View File

@ -11,6 +11,8 @@ import { register } from '@/app/api/authentication';
import { getCaptchaChallenge } from '@/app/api/captcha'; import { getCaptchaChallenge } from '@/app/api/captcha';
export default function RegistrationForm() { export default function RegistrationForm() {
const [errors, setErrors] = useState<string[]>([]);
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [passwordConfirmation, setPasswordConfirmation] = useState<string>(""); const [passwordConfirmation, setPasswordConfirmation] = useState<string>("");
@ -26,6 +28,7 @@ export default function RegistrationForm() {
console.log(id, url); console.log(id, url);
setCaptchaId(id); setCaptchaId(id);
setCaptchaUrl(url); setCaptchaUrl(url);
setCaptchaAnswer("");
} }
}); });
} }
@ -52,10 +55,14 @@ export default function RegistrationForm() {
</FloatLabel> </FloatLabel>
<img className="w-96" src={captchaUrl} alt="captcha" /> <img className="w-96" src={captchaUrl} alt="captcha" />
<FloatLabel className="my-4"> <FloatLabel className="my-4">
<InputText id="captcha" type="text" className='rounded-sm' onChange={(e) => setCaptchaAnswer(e.target.value)} /> <InputText id="captcha" type="text" className='rounded-sm' value={captchaAnswer} onChange={(e) => setCaptchaAnswer(e.target.value)} />
<label htmlFor="captcha">Captcha</label> <label htmlFor="captcha">Captcha</label>
</FloatLabel> </FloatLabel>
{errors.map((error, index) => (
<div key={index} className="text-red-500">{error}</div>
))}
<button <button
className={classNames('primary')} className={classNames('primary')}
@ -70,7 +77,8 @@ export default function RegistrationForm() {
id: captchaId, id: captchaId,
answer: captchaAnswer answer: captchaAnswer
}, },
onRegister: () => { console.log("registered") } onRegister: () => { setErrors([]) },
onError: (errors) => { refreshCaptcha(); setErrors(errors) }
} }
)} )}
> >