Skip to content

Commit ee8ef26

Browse files
committed
feat: integrate react-hot-toast for improved notification handling and replace sonner
1 parent 5901983 commit ee8ef26

File tree

9 files changed

+123
-35
lines changed

9 files changed

+123
-35
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"react": "^19",
6060
"react-dom": "^19",
6161
"react-hook-form": "^7.55.0",
62+
"react-hot-toast": "^2.5.2",
6263
"recharts": "^2.15.1",
6364
"schema-dts": "^1.1.5",
6465
"sonner": "^2.0.5",

src/app/dashboard/_components/ArticleList.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import {
77
DropdownMenu,
88
DropdownMenuContent,
99
DropdownMenuItem,
10-
DropdownMenuLabel,
11-
DropdownMenuSeparator,
1210
DropdownMenuTrigger,
1311
} from "@/components/ui/dropdown-menu";
1412
import VisibilitySensor from "@/components/VisibilitySensor";
@@ -21,7 +19,7 @@ import {
2119
PlusIcon,
2220
} from "@radix-ui/react-icons";
2321

24-
import { useInfiniteQuery } from "@tanstack/react-query";
22+
import { useInfiniteQuery, useMutation } from "@tanstack/react-query";
2523
import { TrashIcon } from "lucide-react";
2624
import Link from "next/link";
2725

@@ -39,6 +37,10 @@ const ArticleList = () => {
3937
},
4038
});
4139

40+
// const deleteMutation = useMutation({
41+
// mutationFn: ac
42+
// })
43+
4244
const appConfirm = useAppConfirm();
4345

4446
return (

src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Metadata } from "next";
2-
import { Toaster } from "@/components/ui/sonner";
2+
import { Toaster } from "@/components/toast";
33
import "../styles/app.css";
44

55
import * as sessionActions from "@/backend/services/session.actions";

src/app/test/page.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
"use client";
22

3-
import { useLoginPopup } from "@/components/app-login-popup";
4-
import React from "react";
3+
import { myBookmarks } from "@/backend/services/bookmark.action";
4+
import { toast } from "@/components/toast";
5+
import { actionPromisify, sleep } from "@/lib/utils";
56

67
const Page = () => {
7-
const pp = useLoginPopup();
88
return (
99
<div>
10-
<button onClick={() => pp.show()}>open</button>
10+
<button
11+
onClick={async () => {
12+
const res = await toast.promise(
13+
myBookmarks({ limit: 1, page: 1, offset: 0 })
14+
);
15+
toast.success(res!);
16+
}}
17+
>
18+
open
19+
</button>
1120
</div>
1221
);
1322
};

src/backend/services/article.actions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ArticleRepositoryInput } from "./inputs/article.input";
1616
import { authID } from "./session.actions";
1717
import { syncTagsWithArticles } from "./tag.action";
1818
import { addDays } from "date-fns";
19+
import { ActionResponse } from "../models/action-contracts";
1920

2021
export async function createMyArticle(
2122
_input: z.infer<typeof ArticleRepositoryInput.createMyArticleInput>
@@ -184,7 +185,9 @@ export async function updateMyArticle(
184185
}
185186
}
186187

187-
export const scheduleArticleDelete = async (article_id: string) => {
188+
export const scheduleArticleDelete = async (
189+
article_id: string
190+
): Promise<ActionResponse<unknown>> => {
188191
try {
189192
const session_userID = await authID();
190193
if (!session_userID) {

src/components/providers/CommonProviders.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ThemeProvider } from "next-themes";
1212
import { AppConfirmProvider } from "../app-confirm";
1313
import { AppAlertProvider } from "../app-alert";
1414
import { AppLoginPopupProvider } from "../app-login-popup";
15+
import { Toaster } from "../toast";
1516

1617
interface Props {
1718
session: SessionResult;
@@ -28,7 +29,10 @@ const CommonProviders: React.FC<PropsWithChildren<Props>> = ({
2829
<AppConfirmProvider>
2930
<AppAlertProvider>
3031
<AppLoginPopupProvider>
31-
<ThemeProvider attribute="data-theme">{children}</ThemeProvider>
32+
<ThemeProvider attribute="data-theme">
33+
<Toaster />
34+
{children}
35+
</ThemeProvider>
3236
</AppLoginPopupProvider>
3337
</AppAlertProvider>
3438
</AppConfirmProvider>

src/components/toast.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use client";
2+
3+
import React from "react";
4+
import _toast, {
5+
Toaster as ReactHotToast,
6+
ToasterProps,
7+
} from "react-hot-toast";
8+
9+
const toast = {
10+
success: function (message: string) {
11+
_toast.success(message);
12+
},
13+
error: function (message: string) {
14+
_toast.error(message);
15+
},
16+
promise: function <T = unknown>(
17+
promise: Promise<T> | (() => Promise<T>),
18+
config?: {
19+
loading: string;
20+
success?: string;
21+
error?: string;
22+
}
23+
) {
24+
return _toast.promise(promise, {
25+
loading: config?.loading ?? "Loading",
26+
success: config?.success ?? "Successful",
27+
error: config?.error ?? "Unsuccessful",
28+
});
29+
},
30+
// custom: function (() => React.ReactNo) {
31+
// toast((t) => (
32+
// <span>
33+
// Custom and <b>bold</b>
34+
// <button onClick={() => toast.dismiss(t.id)}>
35+
// Dismiss
36+
// </button>
37+
// </span>
38+
// ));
39+
// },
40+
};
41+
42+
const Toaster = (props: ToasterProps) => {
43+
return <ReactHotToast {...props} />;
44+
};
45+
46+
export { toast, Toaster };

src/components/ui/sonner.tsx

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/lib/utils.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { ActionResponse } from "@/backend/models/action-contracts";
12
import { clsx, type ClassValue } from "clsx";
3+
import { toast } from "sonner";
24
import { twMerge } from "tailwind-merge";
35
import { z, ZodAnyDef, ZodObject } from "zod";
46

@@ -110,3 +112,49 @@ export function filterUndefined<T>(
110112
Object.entries(mapping).filter(([_, value]) => value !== undefined)
111113
) as Partial<Record<string, any>>;
112114
}
115+
116+
// Improved with automatic type inference
117+
export const actionPromisify = async <T = any>(
118+
action: Promise<ActionResponse<T>>
119+
): Promise<T> => {
120+
const promise = new Promise<T>(async (resolve, reject) => {
121+
try {
122+
const resolvedAction = await action;
123+
124+
if (!resolvedAction) {
125+
reject("Action returned undefined");
126+
return;
127+
}
128+
129+
if (!resolvedAction.success) {
130+
// @ts-ignore
131+
reject(resolvedAction.error ?? "Unknown error occurred");
132+
return;
133+
}
134+
135+
if (resolvedAction.success) {
136+
resolve(resolvedAction.data);
137+
return;
138+
}
139+
} catch (error) {
140+
if (error instanceof Error) {
141+
reject(error.message);
142+
} else {
143+
reject("An unexpected error occurred");
144+
}
145+
}
146+
});
147+
148+
// if (options?.withToast) {
149+
// toast.promise(promise, {
150+
// loading: options?.messages?.loading ?? "Loading...",
151+
// success: options?.messages?.success ?? "Success!",
152+
// error: (errorMsg: string) => errorMsg || "Operation failed",
153+
// });
154+
// }
155+
156+
return promise;
157+
};
158+
159+
export const sleep = (ms: number) =>
160+
new Promise((resolve) => setTimeout(() => resolve("Hello"), ms));

0 commit comments

Comments
 (0)