refactor(web): move toast call sites to the new component

Swaps sonner's toast.success/error/loading/dismiss for the new toast.add({ type,
description }) and toast.close across dialogs, auth pages, the builder, the
dashboard and the applications views. Behaviour is unchanged.
This commit is contained in:
Amruth Pillai
2026-08-17 22:32:32 +02:00
parent 170550ed59
commit 23ceee2148
51 changed files with 472 additions and 431 deletions
+2 -2
View File
@@ -17,7 +17,7 @@ import { createRootRouteWithContext, HeadContent, Outlet, useRouterState } from
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
import { domAnimation, LazyMotion, MotionConfig } from "motion/react";
import { useEffect } from "react";
import { Toaster } from "@reactive-resume/ui/components/sonner";
import { Toaster } from "@reactive-resume/ui/components/toast";
import { TooltipProvider } from "@reactive-resume/ui/components/tooltip";
import { BreakpointIndicator } from "@/components/layout/breakpoint-indicator";
import { DonationToast } from "@/components/ui/donation-toast";
@@ -136,7 +136,7 @@ function RootComponent() {
{!isBuilder && <DonationToast />}
<DialogManager />
<CommandPalette />
<Toaster richColors position="bottom-center" />
<Toaster />
{import.meta.env.DEV && <BreakpointIndicator />}
{import.meta.env.DEV && (
@@ -5,12 +5,12 @@ import { ArrowRightIcon, ChatCircleDotsIcon, FilePlusIcon, GearSixIcon } from "@
import { useMutation, useQuery } from "@tanstack/react-query";
import { Link, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { toast } from "sonner";
import { useIsClient } from "usehooks-ts";
import { Badge } from "@reactive-resume/ui/components/badge";
import { Button } from "@reactive-resume/ui/components/button";
import { Label } from "@reactive-resume/ui/components/label";
import { Spinner } from "@reactive-resume/ui/components/spinner";
import { toast } from "@reactive-resume/ui/components/toast";
import { Combobox } from "@/components/ui/combobox";
import { useHasUsableAiProvider } from "@/features/settings/integrations/hooks/use-has-usable-ai-provider";
import { getOrpcErrorMessage } from "@/libs/error-message";
@@ -176,15 +176,16 @@ export function NewThreadSetup({ resumeId }: NewThreadSetupProps) {
void navigate({ to: "/agent/$threadId", params: { threadId: thread.id } });
},
onError: (error) =>
toast.error(
getOrpcErrorMessage(error, {
toast.add({
type: "error",
description: getOrpcErrorMessage(error, {
byCode: {
PRECONDITION_FAILED: t`AI agent setup is unavailable until REDIS_URL and ENCRYPTION_SECRET are configured.`,
BAD_REQUEST: t`Set up an AI provider before starting a thread.`,
},
fallback: t`Failed to start agent thread.`,
}),
),
}),
},
)
}
@@ -5,8 +5,8 @@ import { Trans } from "@lingui/react/macro";
import { ArrowSquareOutIcon, CircleNotchIcon, FilePdfIcon, MinusIcon, PlusIcon } from "@phosphor-icons/react";
import { Link } from "@tanstack/react-router";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { Button } from "@reactive-resume/ui/components/button";
import { toast } from "@reactive-resume/ui/components/toast";
import { Tooltip, TooltipContent, TooltipTrigger } from "@reactive-resume/ui/components/tooltip";
import { downloadWithAnchor, generateFilename } from "@reactive-resume/utils/file";
import { createResumePdfBlob } from "@/features/resume/export/pdf-document";
@@ -74,7 +74,10 @@ export function ResumePane({ resume }: ResumePaneProps) {
if (!resume) return;
const filename = generateFilename(resume.name || resume.data.basics.name || resume.id, "pdf");
const toastId = toast.loading(t`Please wait while your PDF is being generated…`);
const toastId = toast.add({
type: "loading",
description: t`Please wait while your PDF is being generated…`,
});
setIsPrinting(true);
@@ -82,10 +85,10 @@ export function ResumePane({ resume }: ResumePaneProps) {
const blob = await createResumePdfBlob(resume.data);
downloadWithAnchor(blob, filename);
} catch {
toast.error(t`There was a problem while generating the PDF, please try again.`);
toast.add({ type: "error", description: t`There was a problem while generating the PDF, please try again.` });
} finally {
setIsPrinting(false);
toast.dismiss(toastId);
toast.close(toastId);
}
}, [resume]);
@@ -13,7 +13,6 @@ import {
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Link, useNavigate } from "@tanstack/react-router";
import { useMemo } from "react";
import { toast } from "sonner";
import { Button } from "@reactive-resume/ui/components/button";
import {
DropdownMenu,
@@ -22,6 +21,7 @@ import {
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { ScrollArea } from "@reactive-resume/ui/components/scroll-area";
import { toast } from "@reactive-resume/ui/components/toast";
import { cn } from "@reactive-resume/utils/style";
import { useConfirm } from "@/hooks/use-confirm";
import { getOrpcErrorMessage } from "@/libs/error-message";
@@ -62,7 +62,11 @@ function ThreadActions({ thread, activeThreadId }: ThreadActionsProps) {
});
}
},
onError: (error) => toast.error(getOrpcErrorMessage(error, { fallback: t`Failed to archive thread.` })),
onError: (error) =>
toast.add({
type: "error",
description: getOrpcErrorMessage(error, { fallback: t`Failed to archive thread.` }),
}),
},
);
};
@@ -81,7 +85,11 @@ function ThreadActions({ thread, activeThreadId }: ThreadActionsProps) {
await queryClient.invalidateQueries({ queryKey: orpc.agent.threads.list.queryKey() });
if (activeThreadId === thread.id) void navigate({ to: "/agent" });
},
onError: (error) => toast.error(getOrpcErrorMessage(error, { fallback: t`Failed to delete thread.` })),
onError: (error) =>
toast.add({
type: "error",
description: getOrpcErrorMessage(error, { fallback: t`Failed to delete thread.` }),
}),
},
);
};
@@ -16,7 +16,6 @@ import { useHotkey } from "@tanstack/react-hotkeys";
import { useNavigate } from "@tanstack/react-router";
import { m } from "motion/react";
import { useControls, useTransformComponent } from "react-zoom-pan-pinch";
import { toast } from "sonner";
import { useCopyToClipboard } from "usehooks-ts";
import { Button } from "@reactive-resume/ui/components/button";
import {
@@ -25,6 +24,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { toast } from "@reactive-resume/ui/components/toast";
import { Tooltip, TooltipContent, TooltipTrigger } from "@reactive-resume/ui/components/tooltip";
import { cn } from "@reactive-resume/utils/style";
import {
@@ -108,7 +108,7 @@ export function BuilderDock({ pageLayout, onTogglePageLayout }: BuilderDockProps
title={t`Copy URL`}
onClick={async () => {
await copyToClipboard(publicUrl);
toast.success(t`A link to your resume has been copied to clipboard.`);
toast.add({ type: "success", description: t`A link to your resume has been copied to clipboard.` });
}}
/>
</m.div>
@@ -16,7 +16,6 @@ import {
} from "@phosphor-icons/react";
import { useMutation } from "@tanstack/react-query";
import { Link, useNavigate } from "@tanstack/react-router";
import { toast } from "sonner";
import { match } from "ts-pattern";
import { Button } from "@reactive-resume/ui/components/button";
import {
@@ -26,6 +25,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { toast } from "@reactive-resume/ui/components/toast";
import { useDialogStore } from "@/dialogs/store";
import {
useCurrentBuilderResumeSelector,
@@ -205,7 +205,7 @@ function BuilderHeaderDropdown() {
});
},
onError: (error) => {
toast.error(getResumeErrorMessage(error));
toast.add({ type: "error", description: getResumeErrorMessage(error) });
},
},
);
@@ -218,17 +218,17 @@ function BuilderHeaderDropdown() {
if (!confirmation) return;
const toastId = toast.loading(t`Deleting your resume...`);
const toastId = toast.add({ type: "loading", description: t`Deleting your resume...` });
deleteResume(
{ id },
{
onSuccess: () => {
toast.success(t`Your resume has been deleted successfully.`, { id: toastId });
toast.add({ type: "success", description: t`Your resume has been deleted successfully.`, id: toastId });
void navigate({ to: "/dashboard/resumes", search: { sort: "lastUpdatedAt", tags: [] } });
},
onError: (error) => {
toast.error(getResumeErrorMessage(error), { id: toastId });
toast.add({ type: "error", description: getResumeErrorMessage(error), id: toastId });
},
},
);
@@ -1,9 +1,8 @@
import { t } from "@lingui/core/macro";
import { FloppyDiskIcon } from "@phosphor-icons/react";
import { useHotkey } from "@tanstack/react-hotkeys";
import { Suspense, useState } from "react";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { toast } from "sonner";
import { toast } from "@reactive-resume/ui/components/toast";
import { LoadingScreen } from "@/components/layout/loading-screen";
import { ResumePreview } from "@/features/resume/preview/preview";
import { BuilderDock } from "./dock";
@@ -14,7 +13,11 @@ export function PreviewPage() {
const [pageLayout, setPageLayout] = useState(DEFAULT_BUILDER_PREVIEW_PAGE_LAYOUT);
useHotkey("Mod+S", () => {
toast.info(t`Your changes are saved automatically.`, { id: "auto-save", icon: <FloppyDiskIcon /> });
toast.add({
type: "info",
description: t`Your changes are saved automatically.`,
id: "auto-save",
});
});
return (
@@ -5,7 +5,6 @@ import { Trans } from "@lingui/react/macro";
import { ClockCounterClockwiseIcon } from "@phosphor-icons/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import { Button } from "@reactive-resume/ui/components/button";
import {
DropdownMenu,
@@ -16,6 +15,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@reactive-resume/ui/components/dropdown-menu";
import { toast } from "@reactive-resume/ui/components/toast";
import { useResumeStore } from "@/features/resume/builder/draft";
import { useConfirm } from "@/hooks/use-confirm";
import { getResumeErrorMessage } from "@/libs/error-message";
@@ -53,9 +53,9 @@ export function BuilderVersionHistory({ resumeId }: BuilderVersionHistoryProps)
replaceResumeFromServer(restored as Resume);
queryClient.setQueryData(orpc.resume.getById.queryOptions({ input: { id: resumeId } }).queryKey, restored);
void queryClient.invalidateQueries({ queryKey: orpc.resume.listVersions.queryKey({ input: { resumeId } }) });
toast.success(t`Your resume has been restored to the selected version.`);
toast.add({ type: "success", description: t`Your resume has been restored to the selected version.` });
} catch (error) {
toast.error(getResumeErrorMessage(error));
toast.add({ type: "error", description: getResumeErrorMessage(error) });
}
};
@@ -4,12 +4,12 @@ import { Trans } from "@lingui/react/macro";
import { LockSimpleIcon } from "@phosphor-icons/react";
import { useMutation } from "@tanstack/react-query";
import { Fragment, useCallback, useRef } from "react";
import { toast } from "sonner";
import { match } from "ts-pattern";
import { Avatar, AvatarFallback, AvatarImage } from "@reactive-resume/ui/components/avatar";
import { Button } from "@reactive-resume/ui/components/button";
import { ScrollArea } from "@reactive-resume/ui/components/scroll-area";
import { Separator } from "@reactive-resume/ui/components/separator";
import { toast } from "@reactive-resume/ui/components/toast";
import { Tooltip, TooltipContent, TooltipTrigger } from "@reactive-resume/ui/components/tooltip";
import { getInitials } from "@reactive-resume/utils/string";
import { useCurrentResume, useIsResumeLocked, usePatchResume } from "@/features/resume/builder/draft";
@@ -98,7 +98,7 @@ function LockBanner() {
});
},
onError: (error) => {
toast.error(getResumeErrorMessage(error));
toast.add({ type: "error", description: getResumeErrorMessage(error) });
},
},
);
@@ -13,7 +13,6 @@ import {
import { useMutation } from "@tanstack/react-query";
import { useRef, useState } from "react";
import Cropper from "react-easy-crop";
import { toast } from "sonner";
import { pictureSchema } from "@reactive-resume/schema/resume/data";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { Button } from "@reactive-resume/ui/components/button";
@@ -35,6 +34,7 @@ import {
InputGroupText,
} from "@reactive-resume/ui/components/input-group";
import { Slider } from "@reactive-resume/ui/components/slider";
import { toast } from "@reactive-resume/ui/components/toast";
import "react-easy-crop/react-easy-crop.css";
import { ColorPicker } from "@/components/input/color-picker";
import { useCurrentBuilderResumeSelector, useUpdateResumeData } from "@/features/resume/builder/draft";
@@ -490,26 +490,27 @@ function PictureSectionForm() {
};
const uploadPictureFile = (file: File) => {
const toastId = toast.loading(t`Uploading picture…`);
const toastId = toast.add({ type: "loading", description: t`Uploading picture…` });
uploadFile(file, {
onSuccess: ({ url }) => {
form.setFieldValue("url", url);
handleAutoSave();
toast.dismiss(toastId);
toast.close(toastId);
if (fileInputRef.current) fileInputRef.current.value = "";
},
onError: (error) => {
toast.error(
getReadableErrorMessage(
toast.add({
type: "error",
description: getReadableErrorMessage(
error,
t({
comment: "Fallback toast when uploading profile picture for resume fails",
message: "Failed to upload picture. Please try again.",
}),
),
{ id: toastId },
);
id: toastId,
});
},
});
};
@@ -3,13 +3,12 @@ import { Trans } from "@lingui/react/macro";
import { ArrowRightIcon, InfoIcon, LightningIcon, SparkleIcon } from "@phosphor-icons/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { toast } from "sonner";
import { match } from "ts-pattern";
import { Alert, AlertDescription } from "@reactive-resume/ui/components/alert";
import { Badge } from "@reactive-resume/ui/components/badge";
import { Button } from "@reactive-resume/ui/components/button";
import { toast } from "@reactive-resume/ui/components/toast";
import { useResume } from "@/features/resume/builder/draft";
import { getOrpcErrorMessage } from "@/libs/error-message";
import { orpc } from "@/libs/orpc/client";
import { SectionBase } from "../shared/section-base";
@@ -62,27 +61,10 @@ export function ResumeAnalysisSectionBuilder() {
...orpc.ai.analyzeResume.mutationOptions(),
onSuccess: (analysis) => {
queryClient.setQueryData(orpc.resume.analysis.getById.queryKey({ input: { id: resumeId } }), analysis);
toast.success(t`Resume analysis complete.`);
toast.add({ type: "success", description: t`Resume analysis complete.` });
},
onError: (error) => {
toast.error(t`Failed to analyze resume.`, {
description: getOrpcErrorMessage(error, {
byCode: {
BAD_REQUEST: t({
comment: "Error description when AI returns invalid resume analysis format",
message: "The AI returned an invalid analysis format. Please try again.",
}),
BAD_GATEWAY: t({
comment: "Error description when AI provider cannot be reached during resume analysis",
message: "Could not reach the AI provider. Please try again.",
}),
},
fallback: t({
comment: "Fallback error description when resume analysis request fails",
message: "Something went wrong while analyzing your resume.",
}),
}),
});
onError: (_error) => {
toast.add({ type: "error", description: t`Failed to analyze resume.` });
},
});
@@ -4,12 +4,12 @@ import { ORPCError } from "@orpc/client";
import { ClipboardIcon, LockSimpleIcon, LockSimpleOpenIcon } from "@phosphor-icons/react";
import { useMutation } from "@tanstack/react-query";
import { useCallback } from "react";
import { toast } from "sonner";
import { useCopyToClipboard } from "usehooks-ts";
import { Button } from "@reactive-resume/ui/components/button";
import { Input } from "@reactive-resume/ui/components/input";
import { Label } from "@reactive-resume/ui/components/label";
import { Switch } from "@reactive-resume/ui/components/switch";
import { toast } from "@reactive-resume/ui/components/toast";
import { useCurrentResume, usePatchResume } from "@/features/resume/builder/draft";
import { useConfirm } from "@/hooks/use-confirm";
import { usePrompt } from "@/hooks/use-prompt";
@@ -33,7 +33,7 @@ export function SharingSectionBuilder() {
const onCopyUrl = useCallback(async () => {
await copyToClipboard(publicUrl);
toast.success(t`A link to your resume has been copied to clipboard.`);
toast.add({ type: "success", description: t`A link to your resume has been copied to clipboard.` });
}, [publicUrl, copyToClipboard]);
const onTogglePublic = useCallback(
@@ -45,7 +45,7 @@ export function SharingSectionBuilder() {
});
} catch (error) {
const message = error instanceof ORPCError ? error.message : t`Something went wrong. Please try again.`;
toast.error(message);
toast.add({ type: "error", description: message });
}
},
[patchResume, resume.id, updateResume],
@@ -64,19 +64,19 @@ export function SharingSectionBuilder() {
if (!value) return;
const password = value.trim();
if (!password) return toast.error(t`Password cannot be empty.`);
if (!password) return toast.add({ type: "error", description: t`Password cannot be empty.` });
const toastId = toast.loading(t`Enabling password protection...`);
const toastId = toast.add({ type: "loading", description: t`Enabling password protection...` });
try {
await setPassword({ id: resume.id, password });
patchResume((draft) => {
draft.hasPassword = true;
});
toast.success(t`Password protection has been enabled.`, { id: toastId });
toast.add({ type: "success", description: t`Password protection has been enabled.`, id: toastId });
} catch (error) {
const message = error instanceof ORPCError ? error.message : t`Something went wrong. Please try again.`;
toast.error(message, { id: toastId });
toast.add({ type: "error", description: message, id: toastId });
}
}, [patchResume, prompt, resume.id, setPassword]);
@@ -90,17 +90,17 @@ export function SharingSectionBuilder() {
});
if (!confirmation) return;
const toastId = toast.loading(t`Removing password protection...`);
const toastId = toast.add({ type: "loading", description: t`Removing password protection...` });
try {
await removePassword({ id: resume.id });
patchResume((draft) => {
draft.hasPassword = false;
});
toast.success(t`Password protection has been disabled.`, { id: toastId });
toast.add({ type: "success", description: t`Password protection has been disabled.`, id: toastId });
} catch (error) {
const message = error instanceof ORPCError ? error.message : t`Something went wrong. Please try again.`;
toast.error(message, { id: toastId });
toast.add({ type: "error", description: message, id: toastId });
}
}, [confirm, patchResume, removePassword, resume.hasPassword, resume.id]);
@@ -1,7 +1,7 @@
import type { RouterOutput } from "@/libs/orpc/client";
import { t } from "@lingui/core/macro";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import { toast } from "@reactive-resume/ui/components/toast";
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import { getResumeErrorMessage } from "@/libs/error-message";
@@ -25,7 +25,7 @@ export function useResumeMenuActions(resume: Resume) {
setLockedResume(
{ id: resume.id, isLocked: !resume.isLocked },
{ onError: (error) => toast.error(getResumeErrorMessage(error)) },
{ onError: (error) => toast.add({ type: "error", description: getResumeErrorMessage(error) }) },
);
};
@@ -35,12 +35,13 @@ export function useResumeMenuActions(resume: Resume) {
});
if (!confirmed) return;
const toastId = toast.loading(t`Deleting your resume...`);
const toastId = toast.add({ type: "loading", description: t`Deleting your resume...` });
deleteResume(
{ id: resume.id },
{
onSuccess: () => toast.success(t`Your resume has been deleted successfully.`, { id: toastId }),
onError: (error) => toast.error(getResumeErrorMessage(error), { id: toastId }),
onSuccess: () =>
toast.add({ type: "success", description: t`Your resume has been deleted successfully.`, id: toastId }),
onError: (error) => toast.add({ type: "error", description: getResumeErrorMessage(error), id: toastId }),
},
);
};