import React from 'react'; const humanize = (key) => key.replace(/_/g, ' ').replace(/^./, (c) => c.toUpperCase()); // Devise's `errors` ({field: [messages]}) split into the messages for the // fields named, to show against their inputs, and the rest (the captcha, say), // to show above the form. export function fieldErrors(data, fields) { const byField = {}; const other = []; Object.entries((data && data.errors) || {}).forEach(([key, messages]) => { messages.forEach((message) => { if (fields.includes(key)) { byField[key] = [...(byField[key] || []), message]; } else { other.push(key === 'base' ? message : `${humanize(key)} ${message}`); } }); }); return {byField, other}; } function Errors({id, messages}) { if (messages.length === 0) return null; return
{messages.join(', ')}
; } // A labelled input with its errors under it, wired up for assistive technology. export function TextField({id, label, hint, errors = [], ...inputProps}) { const describedBy = [hint ? `${id}-hint` : null, errors.length > 0 ? `${id}-errors` : null].filter(Boolean).join(' ') || undefined; return (
0 ? ' is-invalid' : ''}`} aria-describedby={describedBy} aria-invalid={errors.length > 0 || undefined} {...inputProps} /> {hint ?
{hint}
: null}
); } // A checkbox; `label` may contain a link. export function CheckField({id, label, errors = [], ...inputProps}) { return (
0 ? ' is-invalid' : ''}`} aria-describedby={errors.length > 0 ? `${id}-errors` : undefined} {...inputProps} />
); } export function FormErrors({messages}) { if (messages.length === 0) return null; return (
{messages.map((message) =>
{message}
)}
); } // The foot of both dialogs: the submit button, and the link across to the other dialog. export function AuthFooter({busy, label, busyLabel, prompt, switchLabel, switchHref, onSwitch}) { return (