import { useState } from 'react' import { api } from '../api.js' // Sign in, or join with an invitation. // // Both live on this screen because they answer the same question — "let me in" // — and the person arriving with a code has no account yet, so they cannot be // asked to sign in first. That is the same reason the endpoint behind it is // unauthenticated, and the same reason a shop PC claims itself before anybody // signs in on it. export default function Login({ onSignedIn }) { const [joining, setJoining] = useState(false) return joining ? setJoining(false)} /> : setJoining(true)} /> } function SignIn({ onSignedIn, onJoin }) { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState('') const [busy, setBusy] = useState(false) const submit = async (e) => { e.preventDefault() setBusy(true) setError('') try { await api.login(email.trim(), password) onSignedIn(await api.me()) } catch (err) { // The server's own wording. It says the same thing for an unknown address // and a wrong password on purpose - telling them apart turns this form // into a way to find out who works at a customer. setError(err.message) setBusy(false) } } return (
) } // Redeeming an invitation. // // Two steps deliberately. The code is checked FIRST, so somebody who has // mistyped it finds out before choosing a password — and so the screen can say // which company they are joining, which is the only thing that makes "is this // the right code" answerable by the person holding it. function Join({ onSignedIn, onCancel }) { const [code, setCode] = useState('') const [invite, setInvite] = useState(null) const [fullName, setFullName] = useState('') const [password, setPassword] = useState('') const [confirm, setConfirm] = useState('') const [error, setError] = useState('') const [busy, setBusy] = useState(false) const check = async (e) => { e.preventDefault() setBusy(true); setError('') try { const prev = await api.previewInvitation(code.trim()) setInvite(prev) setFullName(prev.full_name || '') } catch (err) { // Unknown, expired, spent and withdrawn are one message from the server. // The difference only helps somebody guessing codes, and the next step is // the same in all four cases: ask for a new one. setError(err.message) } finally { setBusy(false) } } const join = async (e) => { e.preventDefault() if (password !== confirm) { setError('Those two passwords are not the same.') return } setBusy(true); setError('') try { // The address and the role are not sent. They belong to the invitation. const user = await api.register({ code: code.trim(), full_name: fullName, password }) onSignedIn(user) } catch (err) { setError(err.message) setBusy(false) } } return (
) }