Spreadsheet-UX op een echte relationele engine

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

4.1 kB Ruw bestand
import { useState, type ReactNode } from 'react';
import { api } from '../api/client';
import { usePublicConfig } from '../api/hooks';
import { useI18n } from '../i18n';
import { useSession } from '../state/session';
import { useToasts } from '../state/toasts';
import { Modal } from './Modal';

export function AccountDialog({ onClose }: { onClose(): void }): ReactNode {
  const session = useSession();
  const toasts = useToasts();
  const install = usePublicConfig();
  const { t } = useI18n();

  const [current, setCurrent] = useState('');
  const [next, setNext] = useState('');
  const [confirm, setConfirm] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [sending, setSending] = useState(false);

  // Only worth asking about where a confirmation could actually be delivered.
  const askToConfirm =
    install.data?.emailEnabled === true && session.account?.verified === false;

  async function sendConfirmation() {
    setSending(true);
    try {
      await api.sendVerification();
      toasts.push(t('account.confirmationSent', session.account?.email ?? ''), 'success');
    } catch (err) {
      toasts.fail(err);
    } finally {
      setSending(false);
    }
  }

  const mismatch = confirm.length > 0 && next !== confirm;
  const canSave = !busy && current.length > 0 && next.length >= 10 && next === confirm;

  async function save() {
    setBusy(true);
    setError(null);
    try {
      await api.changePassword(current, next);
      toasts.push(t('account.passwordChanged'), 'success');
      onClose();
    } catch (err) {
      setError(err instanceof Error ? err.message : t('account.passwordChangeFailed'));
    } finally {
      setBusy(false);
    }
  }

  return (
    <Modal
      title={t('account.title')}
      onClose={onClose}
      footer={
        <>
          <button className="btn" onClick={onClose} disabled={busy}>
            {t('common.close')}
          </button>
          <button className="btn primary" onClick={() => void save()} disabled={!canSave}>
            {busy ? t('common.saving') : t('account.changePassword')}
          </button>
        </>
      }
    >
      <div className="mb">
        <div className="hint">
          {t('account.signedInAs')} <b>{session.account?.email}</b>
          {session.workspace &&
            t('account.roleIn', t(`role.${session.workspace.role}`), session.workspace.name)}
          .
        </div>
        {session.workspaces.length > 1 && (
          <div className="hint">{t('account.otherWorkspaces', session.workspaces.length - 1)}</div>
        )}

        {askToConfirm && (
          <div className="notice">
            <div className="hint">{t('account.unconfirmed')}</div>
            <div className="rowend">
              <button className="btn" onClick={() => void sendConfirmation()} disabled={sending}>
                {sending ? t('common.sending') : t('account.sendConfirmation')}
              </button>
            </div>
          </div>
        )}

        {error && <div className="formerror">{error}</div>}

        <label>
          {t('account.currentPassword')}
          <input
            type="password"
            value={current}
            onChange={(e) => setCurrent(e.target.value)}
            autoComplete="current-password"
          />
        </label>

        <label>
          {t('account.newPassword')}
          <input
            type="password"
            value={next}
            onChange={(e) => setNext(e.target.value)}
            autoComplete="new-password"
          />
          {next.length > 0 && next.length < 10 && (
            <span className="hint bad">{t('account.tooShort')}</span>
          )}
        </label>

        <label>
          {t('account.confirmNewPassword')}
          <input
            type="password"
            value={confirm}
            onChange={(e) => setConfirm(e.target.value)}
            autoComplete="new-password"
          />
          {mismatch && <span className="hint bad">{t('account.mismatch')}</span>}
        </label>

        <div className="hint">{t('account.signsOutOthers')}</div>
      </div>
    </Modal>
  );
}