Files
frontend/src/pages/card/ui/CardPage/CardHistory.view.tsx
T
ksv741 e38ecd87d6
Frontend verify / commits (pull_request) Successful in 39s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Failing after 5m50s
fix(auth,bank): guard switch-auth nav and surface banking retry
AuthForm disables the switch-auth Link while submit is pending so pointer
click and Enter can no longer leave the auth route and lose the draft/error
context. The form restores a true Link after the request settles.

CardsPage/CardPage distinguish cold vs cached banking failures:
- cold card/section errors expose accessible alert + retry
- cached failures keep the previous data and offer a section GET retry
- summary-only/history-only outages do not hide successful sections
- notFound still skips the retry CTA

Tests cover the pending link guard, cold/cached cards error with retry,
summary failure with retry, and the prerequisite card list recovery.

Closes #56, #57
2026-10-14 10:43:34 +03:00

139 lines
2.9 KiB
TypeScript

import {
ActionIcon,
Anchor,
Group,
Skeleton,
Stack,
Text,
} from "@mantine/core";
import {
DeleteIcon,
EditIcon,
OperationIcon,
} from "@wealthledger/icons";
import { EntryCard, LoadingSkeleton } from "@wealthledger/ui";
import { type FC } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import {
BankFeedback,
bankOperationAppearance,
} from "@/entities/bank-card";
import type { CardPageVm } from "./CardPage.vm";
import classes from "./CardPage.module.css";
type CardHistoryProps = Pick<CardPageVm,
"history"
| "period"
| "rows"
>;
// eslint-disable-next-line sonarjs/function-return-type -- React renders elements, lists and empty output.
const CardHistory: FC<CardHistoryProps> = (props) => {
const vm = props;
const { t } = useTranslation();
if (vm.rows) {
if (vm.rows.length > 0) {
return vm.rows.map((row) => {
const details = row.otherCard
? (
<Anchor
component={Link}
to={`/cards/${row.otherCard}?from=${vm.period.from}&to=${vm.period.to}`}
>
{t("bank.card")}
</Anchor>
)
: undefined;
return (
<section
aria-label={`${row.title} ${row.item.date}`}
className={classes.operation}
key={row.item.id}
>
<EntryCard
actions={(
<Group gap={4} wrap="nowrap">
<ActionIcon
aria-label={t("bank.edit")}
disabled={!row.editable}
onClick={row.onEdit}
size="sm"
title={t("bank.edit")}
variant="subtle"
>
<EditIcon size={18} strokeWidth={1.5} />
</ActionIcon>
<ActionIcon
aria-label={t("bank.delete")}
color="red"
disabled={!row.editable}
onClick={row.onDelete}
size="sm"
title={t("bank.delete")}
variant="subtle"
>
<DeleteIcon size={18} strokeWidth={1.5} />
</ActionIcon>
</Group>
)}
color={row.color}
details={details}
icon={(
<OperationIcon
{...bankOperationAppearance(
row.item.kind,
row.item.categoryName,
)}
/>
)}
initial={row.title.charAt(0)}
metadata={row.metadata}
title={row.title}
tone={row.tone}
value={row.value}
valueLabel={t(`bank.kinds.${row.item.kind}`)}
wrap
/>
</section>
);
});
}
return <Text>{t("bank.noOperations")}</Text>;
}
if (vm.history.error) {
return (
<BankFeedback
cached={Boolean(vm.rows)}
error={vm.history.error}
loadingError={!vm.rows}
retry={vm.history.refetch}
/>
);
}
if (vm.history.loading) {
return (
<LoadingSkeleton label={t("bank.loadingHistory")}>
<Stack>
<Skeleton h={90} />
<Skeleton h={90} />
</Stack>
</LoadingSkeleton>
);
}
return null;
};
export { CardHistory };