feat: add payment and bypass actions to the in-flight vessel registration application table

This commit is contained in:
estifanos
2026-08-22 07:58:08 +00:00
parent ae0dfdece3
commit 3d6deb03f6
2 changed files with 83 additions and 10 deletions

View File

@@ -12,7 +12,14 @@ import {
/** Columns for the applicant's in-flight vessel registration applications. */
export function inFlightColumns(
t: TFunction,
deps: { onOpen: (app: LicenseApplication) => void },
deps: {
onOpen: (app: LicenseApplication) => void;
onPay: (app: LicenseApplication) => void;
onBypass: (app: LicenseApplication) => void;
isPaying: boolean;
bypassEnabled: boolean;
bypassing: boolean;
},
): AdvancedColumn<LicenseApplication>[] {
return [
{
@@ -38,15 +45,49 @@ export function inFlightColumns(
},
{
header: t('applications.table.progress'),
size: 140,
cell: ({ row }) => (
<Progress
value={STATUS_PROGRESS[row.original.status]}
color={STATUS_COLORS[row.original.status]}
size="sm"
radius="xl"
/>
),
size: 300,
cell: ({ row }) => {
const app = row.original;
return (
<Group gap="xs" wrap="nowrap">
<Progress
value={STATUS_PROGRESS[app.status]}
color={STATUS_COLORS[app.status]}
size="sm"
radius="xl"
style={{ flex: 1, minWidth: 60 }}
/>
{/* The fee stops the registration dead, so the payment action sits
on the bar rather than being hidden behind View. */}
{app.status === 'PAYMENT_PENDING' && (
<>
<Button
size="compact-sm"
color="yellow"
loading={deps.isPaying}
onClick={() => deps.onPay(app)}
>
{t('applications.actions.pay', {
amount: Number(app.feeAmount ?? 0).toLocaleString(),
currency: app.feeCurrency,
})}
</Button>
{deps.bypassEnabled && (
<Button
size="compact-sm"
variant="default"
loading={deps.bypassing}
onClick={() => deps.onBypass(app)}
title="Testing only — marks the fee paid"
>
{t('applications.actions.bypass')}
</Button>
)}
</>
)}
</Group>
);
},
},
{
header: '',

View File

@@ -30,11 +30,16 @@ import {
import { StatusBadge, AdvancedTable } from '@ema-platform/ui';
import { inFlightColumns } from '../inFlightColumns';
import {
extractErrorMessage,
TERMINAL_STATUSES,
useApiMutation,
useBypassPaymentMutation,
useGetMyApplicationsQuery,
useGetPaymentCapabilitiesQuery,
} from '@ema-platform/api';
import { notifications } from '@mantine/notifications';
import { authStorage } from '@ema-platform/auth';
import { useApplicationPayment } from '../../payments/hooks/useApplicationPayment';
// ---------------------------------------------------------------------------
// Types
@@ -132,6 +137,9 @@ export function VesselRegistrationPage() {
const navigate = useNavigate();
const { t } = useTranslation();
const { data: applications, isFetching, refetch } = useGetMyApplicationsQuery();
const { pay, isPaying } = useApplicationPayment();
const { data: capabilities } = useGetPaymentCapabilitiesQuery();
const [bypassPayment, { isLoading: bypassing }] = useBypassPaymentMutation();
const [page, setPage] = useState(0);
const [registration, setRegistration] = useState<VesselRegistration | null>(null);
const [fetchTrigger] = useApiMutation<VesselRegistration>();
@@ -155,9 +163,33 @@ export function VesselRegistrationPage() {
!TERMINAL_STATUSES.includes(app.status),
);
async function handleBypass(applicationId: string) {
try {
const result = await bypassPayment(applicationId).unwrap();
notifications.show({
color: 'teal',
title: 'Payment bypassed',
message: `Application is now ${result.status.replace(/_/g, ' ').toLowerCase()}.`,
});
refetch();
} catch (err) {
notifications.show({
color: 'red',
title: 'Bypass failed',
message: extractErrorMessage(err),
});
}
}
const columns = inFlightColumns(t, {
onOpen: (app) =>
navigate(`/licensing/${REGISTRATION_TYPE_KEY}/applications/${app.id}`),
// Paying leaves the SPA for Telebirr — a provider hand-off, not a route change.
onPay: (app) => pay(app.id),
onBypass: (app) => handleBypass(app.id),
isPaying,
bypassEnabled: capabilities?.bypassEnabled ?? false,
bypassing,
});
const certs = registration?.category === 'Sea-going Vessel (International)'