Files
baya-monorepo/client/src/app/[locale]/(private-routes)/(customer)/wallet/WalletInstallments.tsx
T
2026-07-27 22:27:04 +03:30

130 lines
5.4 KiB
TypeScript

'use client';
import { FunctionComponent } from 'react';
import { useLocale, useTranslations } from 'next-intl';
import { Box, Paper, Skeleton, Stack, Typography } from '@mui/material';
import { AppButton, AppIcon, InstallmentScheduleRow, Money, PlaceholderScreen } from '@/components';
import { formatShamsiDate } from '@/utils';
import { useWalletInstallments } from '@/services/bnpl';
import type { WalletInstallmentPlan } from '@/services/bnpl/types';
/**
* D5 · پیگیری اقساط — the Wallet «اقساط» section (active installment plans). It reads
* `useWalletInstallments` and renders **provider-reported** status: an outstanding-balance card
* (terracotta), the next-installment date + a provider hand-off «پرداخت زودهنگام» (early-pay is a
* *provider* action, never a Balinyaar transaction), the per-installment due list with status chips, and
* the ownership note (Balinyaar displays, it does not manage, this schedule). Section body only — the
* page-level heading + tab strip live in `WalletScreen`.
*/
const WalletInstallments: FunctionComponent = () => {
const t = useTranslations('bnpl');
const tc = useTranslations('common');
const { data: plans, isLoading, isError, refetch } = useWalletInstallments();
return (
<Stack sx={{ gap: 2, width: '100%' }}>
{isLoading ? (
<Stack sx={{ gap: 1.5 }}>
<Skeleton variant="rounded" height={128} />
<Skeleton variant="rounded" height={56} />
<Skeleton variant="rounded" height={56} />
</Stack>
) : isError ? (
<Paper elevation={0} sx={{ p: 3, borderRadius: 'var(--bal-radius-md)', border: '1px solid', borderColor: 'divider', textAlign: 'center' }}>
<Stack sx={{ gap: 1, alignItems: 'center' }}>
<AppIcon icon="warning" size={36} color="var(--bal-warning)" />
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
{t('wallet_error_title')}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{t('wallet_error_body')}
</Typography>
<AppButton variant="outlined" color="secondary" onClick={() => refetch()} sx={{ mt: 1 }}>
{tc('retry')}
</AppButton>
</Stack>
</Paper>
) : !plans || plans.length === 0 ? (
<PlaceholderScreen icon="installments" title={t('wallet_empty_title')} description={t('wallet_empty_body')} />
) : (
<Stack sx={{ gap: 3 }}>
{plans.map((plan) => (
<InstallmentPlanSection key={plan.bnplTransactionId} plan={plan} />
))}
</Stack>
)}
</Stack>
);
};
function InstallmentPlanSection({ plan }: { plan: WalletInstallmentPlan }) {
const t = useTranslations('bnpl');
const locale = useLocale();
const providerName = t(`provider_${plan.providerCode}`);
const handleEarlyPay = () => {
// Early-pay is a PROVIDER action — hand off to the provider, never a Balinyaar payment.
if (plan.earlyPayUrl) window.open(plan.earlyPayUrl, '_blank', 'noopener,noreferrer');
};
return (
<Stack sx={{ gap: 1.5 }}>
{/* Outstanding-balance card — terracotta financial accent; contrast text is scheme-stable. */}
<Paper
elevation={0}
sx={{ p: 2.25, borderRadius: 'var(--bal-radius-lg)', backgroundColor: 'var(--bal-secondary)', color: 'var(--bal-secondary-contrast)' }}
>
<Typography variant="caption" sx={{ opacity: 0.85 }}>
{t('outstanding_balance')}
</Typography>
<Money amountIrr={plan.outstandingBalanceIrr} size="lg" sx={{ fontWeight: 800, mt: 0.25 }} />
<Typography variant="caption" sx={{ opacity: 0.85, display: 'block', mt: 0.5 }}>
{plan.serviceLabel}
</Typography>
{plan.nextDueDate ? (
<Stack direction="row" sx={{ justifyContent: 'space-between', alignItems: 'flex-end', mt: 1.5, gap: 1.5 }}>
<Box>
<Typography variant="caption" sx={{ opacity: 0.85 }}>
{t('next_installment')} · {formatShamsiDate(`${plan.nextDueDate}T00:00:00`, locale)}
</Typography>
{plan.nextAmountIrr ? (
<Money amountIrr={plan.nextAmountIrr} size="sm" sx={{ fontWeight: 800 }} />
) : null}
</Box>
{plan.earlyPayUrl ? (
<AppButton
color="primary"
variant="contained"
size="small"
onClick={handleEarlyPay}
sx={{ flex: 'none' }}
>
{t('early_pay')}
</AppButton>
) : null}
</Stack>
) : null}
</Paper>
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
{t('due_dates')}
</Typography>
<Stack sx={{ gap: 1 }}>
{plan.installments.map((row) => (
<InstallmentScheduleRow key={`${row.kind}-${row.sequence}`} row={row} showStatus />
))}
</Stack>
{/* Ownership note: Balinyaar displays, it does not manage, this provider-owned schedule. */}
<Stack direction="row" sx={{ gap: 0.75, alignItems: 'flex-start', px: 0.5 }}>
<AppIcon icon="info" size={16} color="var(--bal-text-secondary)" />
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{t('provider_owned_note', { provider: providerName })}
</Typography>
</Stack>
</Stack>
);
}
export default WalletInstallments;