| import { useEffect, useState } from 'react' |
| import { useI18n } from '../i18n' |
| import { Callout } from '../components/ui' |
| import { listActivity, subscribeActivity, type ActivityEvent, type ActivityKind } from '../activity/store' |
| import { formatDateTime } from '../utils/format' |
|
|
| const TABS: { key: string; kind?: ActivityKind; label: string }[] = [ |
| { key: 'all', label: 'activity.tab.all' }, |
| { key: 'payments', kind: 'payment', label: 'activity.tab.payments' }, |
| { key: 'confirmations', kind: 'confirmation', label: 'activity.tab.confirmations' }, |
| { key: 'biometrics', kind: 'biometrics', label: 'activity.tab.biometrics' }, |
| { key: 'security', kind: 'security-demo', label: 'activity.tab.security' }, |
| ] |
|
|
| const KIND_LABEL: Record<ActivityKind, string> = { |
| payment: 'activity.kind.payment', confirmation: 'activity.kind.confirmation', |
| biometrics: 'activity.kind.biometrics', 'security-demo': 'activity.kind.securityDemo', |
| } |
|
|
| |
| |
| export function ActivityPage() { |
| const { t, locale } = useI18n() |
| const [tab, setTab] = useState('all') |
| const [, force] = useState(0) |
|
|
| useEffect(() => subscribeActivity(() => force((n) => n + 1)), []) |
|
|
| const kind = TABS.find((x) => x.key === tab)?.kind |
| const events: ActivityEvent[] = listActivity(kind) |
|
|
| return ( |
| <section className="page activity" aria-labelledby="act-h"> |
| <div className="row-between"> |
| <h2 id="act-h">{t('activity.title')}</h2> |
| <span className="badge tone-info">{t('activity.sessionOnly')}</span> |
| </div> |
| <p className="muted small">{t('activity.sessionNote')}</p> |
| |
| <div className="tabs" role="tablist" aria-label={t('activity.title')}> |
| {TABS.map((x) => ( |
| <button key={x.key} role="tab" aria-selected={tab === x.key} |
| className={`tab ${tab === x.key ? 'active' : ''}`} onClick={() => setTab(x.key)}> |
| {t(x.label as never)} |
| </button> |
| ))} |
| </div> |
| |
| {events.length === 0 ? ( |
| <Callout tone="info"><span data-testid="activity-empty">{t('activity.empty')}</span></Callout> |
| ) : ( |
| <ul className="activity-list"> |
| {events.map((e) => ( |
| <li key={e.id} className="activity-item" data-testid="activity-item"> |
| <div className="row-between"> |
| <span className="badge tone-info">{t(KIND_LABEL[e.kind] as never)}</span> |
| <span className="muted small">{formatDateTime(e.ts / 1000, locale)}</span> |
| </div> |
| <div className="row-between"> |
| <strong>{e.title}</strong> |
| <span className="badge tone-info">{e.status}</span> |
| </div> |
| {e.amount && <div className="muted small">{e.amount}</div>} |
| {e.reference && <div className="muted small mono">{e.reference}</div>} |
| {e.reasonCodes && e.reasonCodes.length > 0 && ( |
| <div className="muted small">{e.reasonCodes.join(', ')}</div> |
| )} |
| {typeof e.auditVerified === 'boolean' && ( |
| <span className={`badge ${e.auditVerified ? 'tone-ok' : 'tone-bad'}`}> |
| {e.auditVerified ? t('agentic.result.auditVerified') : t('agentic.result.auditFailed')} |
| </span> |
| )} |
| </li> |
| ))} |
| </ul> |
| )} |
| </section> |
| ) |
| } |
|
|