Skip to content

Toast Notification System ​

SkySend uses Sonner for in-app toast notifications. The apps/web/src/lib/toast.tsx module wraps Sonner with two layers:

  1. showToast() - general-purpose helper supporting optional Copy and Docs action buttons.
  2. showKnownErrorToast() - error-specific helper that detects known error patterns and automatically enriches them with a docs link and copy button.

showToast() ​

Use showToast() when a toast needs action buttons (Copy or Docs). For simple toasts without buttons, call Sonner directly (toast.error(), toast.success(), etc.) - the overhead is not worth it.

ts
import { showToast } from "@/lib/toast";

// Simple error (no buttons - use Sonner directly instead):
toast.error(t("upload.failed"));

// Error with a docs link and copy button:
showToast(t("errors.insecureContext"), {
  type: "error",
  description: rawErrorMessage,
  copyText: rawErrorMessage,
  docsUrl: "https://docs.skysend.app/user-guide/troubleshooting#...",
});

Options ​

OptionTypeDescription
type"error" | "warning" | "info" | "success" | "default"Sets the icon and color. Defaults to "default".
descriptionstringSecondary text shown below the title in smaller type.
copyTextstringIf set, a Copy button is shown that copies this text to the clipboard.
docsUrlstringIf set, a Docs button is shown that opens this URL in a new tab.
durationnumberOverride the auto-dismiss timeout in milliseconds.
idstringDeduplication key - a second call with the same ID updates the existing toast instead of opening a new one.

When neither copyText nor docsUrl is provided, showToast() delegates to the native Sonner helpers so the toast benefits from Sonner's built-in animations. When action buttons are needed, it uses Sonner's native toast.error() (or the matching type variant) and passes a ToastActionButtons component as the description node. This keeps Sonner's native layout, close button, and animations intact.

showKnownErrorToast() ​

Use showKnownErrorToast() anywhere a raw error message from the crypto pipeline is shown as a toast. It checks the message against a list of known patterns and enriches matching errors with a docs link and copy button.

ts
import { showKnownErrorToast } from "@/lib/toast";

useEffect(() => {
  if (hook.phase === "error" && hook.error) {
    showKnownErrorToast(hook.error);
  }
}, [hook.phase, hook.error]);

For unknown errors it falls back to toast.error(message).

Known error patterns ​

Patterni18n titleDocs link
importKey / crypto.subtle / subtle is undefinederrors.insecureContextTroubleshooting - HTTPS required
Origin not allowederrors.originNotAllowedTroubleshooting - Origin not allowed
S3 CORS / S3 unreachableerrors.s3CorsErrorTroubleshooting - S3 CORS

Adding a new known error ​

  1. Add a detector in isInsecureContextError() or create a new isXxxError() function in lib/toast.tsx.
  2. Add the corresponding i18n key to en.json and de.json, then to all other language files with AI-generated values (see i18n rules).
  3. Call showToast() with the docsUrl pointing to the relevant docs article.
  4. Add a row to the table above.

ToastActionButtons component ​

The action buttons (Copy, Docs) live in apps/web/src/components/ui/custom-toast.tsx as the exported ToastActionButtons component. It is only used indirectly through showToast() and is not meant to be rendered directly.

The component handles clipboard writes with a navigator.clipboard primary path and a document.execCommand fallback for HTTP contexts where the Clipboard API is unavailable.

Toaster placement ​

The <Toaster /> component is rendered once in App.tsx outside the router. It is configured via apps/web/src/components/ui/sonner.tsx:

  • Position: top-center
  • Close button: enabled (Sonner's native close button, positioned top-right)
  • Theme: follows the user's current theme (dark / light / system) via useTheme()
  • Icons: custom Lucide icons (AlertCircle, AlertTriangle, CheckCircle2, Info) replace Sonner's built-in icons to match the app's design
  • Colors: overridden via CSS in index.css to use the app's card tokens (--color-card, --color-border, --color-card-foreground) in both light and dark mode

i18n ​

Action button labels (Copy, Copied!, Docs) are looked up via common.copy, common.copied, and common.docs in the translation files. Error titles use keys in the errors.* namespace.

When adding a new key, add it to en.json and de.json first - those two are the source of truth. Then add AI-translated values to the remaining language files. A file whose __meta block has "aiGenerated": true needs no further tracking, a file with "aiGeneratedKeys" needs the new key appended to that array.