Long-running job
Progress for exports, imports and uploads.
Imports and exports
Jobs keep running if you leave this page. We'll email you when each one finishes.
Export all employees (CSV)
Exporting employee records798 of 1,248About 1 minute left- 38%
Upload October payslips
payslips-2026-10.zip · 94 of 248 files · 18.6 MB
Import benefits enrolment
Checking rows for errorsExport leave balances (XLSX)
Export finishedDoneFinished at 09:42 · 2.4 MB
longrunning-job.tsx
"use client";
import { useState } from "react";
import { LuDownload, LuFileSpreadsheet, LuUpload } from "react-icons/lu";
import { Badge, Button, CircularProgress, ProgressBar, SpinnerLabel } from "@nexera-ui/react";
/** Long-running job: progress for an export, an upload and an import, with cancel and a finished state. */
export default function LongRunningJob() {
const [cancelled, setCancelled] = useState<string[]>([]);
const cancel = (id: string) => {
setCancelled((ids) => [...ids, id]);
};
const isCancelled = (id: string) => cancelled.includes(id);
const cancelledNote = <Badge tone="neutral" label="Cancelled" />;
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid max-w-3xl gap-4">
<header className="grid gap-1">
<h2 className="text-heading-h2 text-primary">Imports and exports</h2>
<p className="text-body-default text-secondary">
Jobs keep running if you leave this page. We'll email you when each one finishes.
</p>
</header>
<ul className="m-0 grid list-none gap-3 p-0">
<li className="grid gap-3 rounded-xl border border-default bg-surface p-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<h3 className="flex items-center gap-2 text-body-strong text-primary">
<LuDownload aria-hidden="true" />
Export all employees (CSV)
</h3>
{isCancelled("export") ? (
cancelledNote
) : (
<Button
size="sm"
variant="ghost"
onPress={() => {
cancel("export");
}}
>
Cancel export
</Button>
)}
</div>
{isCancelled("export") ? null : (
<ProgressBar
label="Exporting employee records"
labelPosition="top"
size="md"
value={64}
valueLabel="798 of 1,248"
description="About 1 minute left"
/>
)}
</li>
<li className="flex flex-wrap items-center gap-4 rounded-xl border border-default bg-surface p-4">
{isCancelled("upload") ? null : (
<CircularProgress size="md" value={38} aria-label="Uploading October payslips" />
)}
<div className="grid min-w-0 flex-1 basis-48 gap-0.5">
<h3 className="flex items-center gap-2 text-body-strong text-primary">
<LuUpload aria-hidden="true" />
Upload October payslips
</h3>
<p className="text-body-small text-secondary">
payslips-2026-10.zip · 94 of 248 files · 18.6 MB
</p>
</div>
{isCancelled("upload") ? (
cancelledNote
) : (
<Button
size="sm"
variant="ghost"
onPress={() => {
cancel("upload");
}}
>
Cancel upload
</Button>
)}
</li>
<li className="grid gap-2 rounded-xl border border-default bg-surface p-4">
<h3 className="flex items-center gap-2 text-body-strong text-primary">
<LuFileSpreadsheet aria-hidden="true" />
Import benefits enrolment
</h3>
<SpinnerLabel description="benefits-enrolment.xlsx · 312 rows">
Checking rows for errors
</SpinnerLabel>
</li>
<li className="grid gap-3 rounded-xl border border-default bg-surface p-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<h3 className="flex items-center gap-2 text-body-strong text-primary">
<LuDownload aria-hidden="true" />
Export leave balances (XLSX)
</h3>
<Button size="sm" variant="secondary" leadingIcon={<LuDownload />}>
Download
</Button>
</div>
<ProgressBar
label="Export finished"
labelPosition="top"
tone="success"
value={100}
valueLabel="Done"
description="Finished at 09:42 · 2.4 MB"
/>
</li>
</ul>
</div>
</div>
);
}
More states and system messages blocks
- Empty statesFirst use, no results and all done.
- Loading skeletonsPage and panel placeholders while data loads.
- Error pagesNot found, no access and something went wrong.
- Success confirmationA modal that confirms a finished task.
- ToastsShort confirmations with undo.
- Confirm a destructive actionName what will be deleted before deleting it.
- Maintenance and offlineTell people about downtime or lost connection.