Files
kvitt/web/src/features/accounts/PageStates.tsx
T
jacobh2 934eee793c W1 web Home and accounts: W-2 Home, W-3 account detail, W-4 account editor
Home (mirrors S-4, S-3 while the partner has not joined): global balance hero
with the seesaw and §9.3 wording, Settle up, active accounts with balance and
this month's count, collapsed "Arkiverade (n)" kept for the session, empty,
loading and error states, New expense when an active account exists.

Account detail (S-5): header with default split, split bar, archived badge and
balance; expenses and settlements interleaved by date, grouped by month with
household spend (settlement rows carry the allocation, AC-11.7); overflow menu
with Edit, Archive/Unarchive (undo snackbar, D-071) and Delete only when
allowed (AC-6.5); not-found, loading and error states.

Account editor (S-6): name (1-60 code points, name_taken on the field), two
percent fields that always sum to 100 with two decimals, slider and presets,
the "Av 100,00 kr" preview; discard dialog with a title and no body (D-080)
on every navigation away; E-18/E-19 apply the returned row.

The account preset for W-5 and W-7 is the query parameter `account`
(proposed D-144). Tests over a shared snapshot fixture and the fake server.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-21 21:28:43 +02:00

66 lines
2.3 KiB
TypeScript

import { ButtonLink, Button } from '../../components/Button';
import { Card } from '../../components/Card';
import { EmptyState } from '../../components/EmptyState';
import { Icon } from '../../design/Icon';
import { useI18n } from '../../i18n/useI18n';
import { paths } from '../../app/routes';
import { writeErrorKey } from './accountWrites';
import styles from './PageStates.module.css';
/**
* The first snapshot pull (SPEC §15.6 "loading"): design-spec §3.6 hero `Loading` content, announced politely.
* `compact` drops the body line (account pages).
*/
export function LoadingCard({ compact = false }: { compact?: boolean }) {
const { t } = useI18n();
return (
<Card className={styles.loading}>
<div role="status" className={styles.loadingInner}>
<span className={styles.spinnerTile} aria-hidden="true">
<Icon name="sync" size={28} className={styles.spinner} />
</span>
<p className={styles.loadingTitle}>{t('home_loading')}</p>
{compact ? null : <p className={styles.muted}>{t('home_loading_body')}</p>}
</div>
</Card>
);
}
/** A failed read of the snapshot with nothing to show yet (SPEC §15.6 "error, with retry"). */
export function LoadFailedCard({ error, onRetry }: { error: unknown; onRetry: () => void }) {
const { t } = useI18n();
return (
<Card className={styles.failed}>
<div role="alert" className={styles.failedInner}>
<Icon name="warning" filled size={24} className={styles.warning} />
<div className={styles.failedText}>
<h2 className={styles.failedTitle}>{t('web_accounts_load_failed')}</h2>
<p className={styles.muted}>{t(writeErrorKey(error))}</p>
</div>
</div>
<div>
<Button variant="tonal" icon={<Icon name="refresh" size={20} />} onClick={onRetry}>
{t('common_retry')}
</Button>
</div>
</Card>
);
}
/** An account id that is not (or no longer) in the snapshot. */
export function AccountNotFound() {
const { t } = useI18n();
return (
<EmptyState
icon="info"
title={t('error_not_found')}
body={t('web_accounts_not_found_body')}
action={
<ButtonLink to={paths.home} variant="tonal">
{t('web_not_found_action')}
</ButtonLink>
}
/>
);
}