50 lines
1.8 KiB
TypeScript
50 lines
1.8 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { toast } from 'sonner';
|
||
|
|
|
||
|
|
import { ApiError } from '@/lib/api/client';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render an API error as a toast in the consistent platform format:
|
||
|
|
*
|
||
|
|
* ┌─────────────────────────────────────────────┐
|
||
|
|
* │ {plain-text message} │
|
||
|
|
* │ │
|
||
|
|
* │ Error code: EXPENSES_RECEIPT_REQUIRED │
|
||
|
|
* │ Reference ID: ab12-cd34-… [Copy] │
|
||
|
|
* └─────────────────────────────────────────────┘
|
||
|
|
*
|
||
|
|
* Use this anywhere a `useMutation({ onError })` would otherwise just
|
||
|
|
* call `toast.error(err.message)`. Falls back gracefully when the error
|
||
|
|
* isn't an ApiError (network errors, programmer errors, etc.).
|
||
|
|
*/
|
||
|
|
export function toastError(err: unknown, fallback = 'Something went wrong.'): void {
|
||
|
|
if (err instanceof ApiError) {
|
||
|
|
const lines: string[] = [];
|
||
|
|
if (err.code) lines.push(`Error code: ${err.code}`);
|
||
|
|
if (err.requestId) lines.push(`Reference ID: ${err.requestId}`);
|
||
|
|
toast.error(err.message, {
|
||
|
|
description: lines.length > 0 ? lines.join('\n') : undefined,
|
||
|
|
// Long enough to read the message + grab the reference id.
|
||
|
|
duration: 8_000,
|
||
|
|
action: err.requestId
|
||
|
|
? {
|
||
|
|
label: 'Copy ID',
|
||
|
|
onClick: () => {
|
||
|
|
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
||
|
|
void navigator.clipboard.writeText(err.requestId!);
|
||
|
|
toast.success('Reference ID copied');
|
||
|
|
}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
: undefined,
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (err instanceof Error) {
|
||
|
|
toast.error(err.message || fallback);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
toast.error(fallback);
|
||
|
|
}
|