All files / Bandstand/src/components ChangePassword.tsx

0% Statements 0/99
100% Branches 1/1
100% Functions 1/1
0% Lines 0/99

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                                                                                                                                                                                                                                         
'use client'
 
import {signIn, useSession} from 'next-auth/react'
import {useRouter} from 'next/navigation'
import {useState} from 'react'
 
const MIN = 10
 
export default function ChangePassword({
  hasPassword = true,
}: {
  hasPassword?: boolean
}) {
  const router = useRouter()
  const {data: session} = useSession()
  const [current, setCurrent] = useState('')
  const [next, setNext] = useState('')
  const [again, setAgain] = useState('')
  const [msg, setMsg] = useState<{ok: boolean; text: string} | null>(null)
  const [busy, setBusy] = useState(false)
 
  async function submit(e: React.FormEvent) {
    e.preventDefault()
    if (next !== again)
      return setMsg({ok: false, text: 'The new passwords don’t match.'})
    if (next.length < MIN)
      return setMsg({ok: false, text: `Use at least ${MIN} characters.`})
    setBusy(true)
    const r = await fetch('/api/account/password', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({current: hasPassword ? current : undefined, next}),
    })
    if (!r.ok) {
      setBusy(false)
      return setMsg({
        ok: false,
        text:
          (await r.json().catch(() => ({}))).error ?? 'Could not change it.',
      })
    }
    // Changing the password ends every session, this one included: sign back in
    await signIn('credentials', {
      email: session?.user?.email,
      password: next,
      redirect: false,
    })
    setBusy(false)
    setCurrent('')
    setNext('')
    setAgain('')
    setMsg({
      ok: true,
      text: 'Changed. Your other devices will need the new password.',
    })
    router.refresh()
  }
 
  const field = (
    label: string,
    value: string,
    set: (v: string) => void,
    auto: string,
  ) => (
    <label className="flex flex-col gap-1 text-sm text-muted">
      {label}
      <input
        type="password"
        autoComplete={auto}
        required
        value={value}
        onChange={(e) => set(e.target.value)}
        className="min-h-12 rounded-lg border border-line-2 bg-panel px-3 font-mono text-base text-text"
      />
    </label>
  )
 
  return (
    <section className="mt-8">
      <h2 className="text-xl font-extrabold">
        {hasPassword ? 'Change password' : 'Set a password'}
      </h2>
      {!hasPassword && (
        <p className="mt-1 text-muted">
          You sign in with Google. A password lets you sign in without it too.
        </p>
      )}
      <form onSubmit={submit} className="mt-5 flex flex-col gap-4">
        {hasPassword &&
          field('Current password', current, setCurrent, 'current-password')}
        {field(
          `New password (at least ${MIN} characters)`,
          next,
          setNext,
          'new-password',
        )}
        {field('New password again', again, setAgain, 'new-password')}
        {msg && (
          <p
            role="alert"
            className={`text-sm ${msg.ok ? 'text-good' : 'text-bad'}`}
          >
            {msg.text}
          </p>
        )}
        <button
          type="submit"
          disabled={busy}
          className="min-h-12 rounded-xl bg-accent font-extrabold text-on-accent disabled:opacity-50"
        >
          {busy ? 'Saving…' : 'Change password'}
        </button>
      </form>
    </section>
  )
}