A toast offers to undo for seven seconds (or Ctrl/⌘ Z outside a field) by running the opposite command: the card back to its column or the ticket to its status (the workflow may refuse it), the previous assignee, or the ticket back from the trash. The undo itself only says «Deshecho». On the board, from the card's assignee; on the ticket page, status, assignee and trash, which no longer asks for confirmation. The toast is the app's own (UndoToastComponent): pi-snack-bar cannot tell its button from the timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28 lines
1005 B
TypeScript
28 lines
1005 B
TypeScript
/** Outbound port: tell the user how a command went (implemented with toasts by the app). */
|
|
export abstract class Notifications {
|
|
abstract success(message: string): void;
|
|
abstract error(message: string): void;
|
|
/** Done, offering to undo it for a few seconds: `undo` runs if asked. */
|
|
abstract undoable(message: string, undo: () => void): void;
|
|
}
|
|
|
|
/** Message of a failed GraphQL/HTTP call, for the user. */
|
|
export function describeError(error: unknown): string {
|
|
if (
|
|
error &&
|
|
typeof error === 'object' &&
|
|
'errors' in error &&
|
|
Array.isArray((error as { errors: unknown[] }).errors)
|
|
) {
|
|
return (error as { errors: { message: string }[] }).errors
|
|
.map((e) => e.message)
|
|
.join('\n');
|
|
}
|
|
return error instanceof Error ? error.message : String(error);
|
|
}
|
|
|
|
/** The read model is eventually consistent: right after a command, a query may 404 for a few ms. */
|
|
export function isNotFound(error: unknown): boolean {
|
|
return /not found/i.test(describeError(error));
|
|
}
|