Skip to content

Commit d0084df

Browse files
committed
Implemented toast notifications for Save, Vote, CRUD actions
1 parent df81eb9 commit d0084df

File tree

11 files changed

+465
-3
lines changed

11 files changed

+465
-3
lines changed

app/(root)/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22

3+
import { Toaster } from "@/components/ui/toaster";
34
import Navbar from "@/components/shared/navbar/Navbar";
45
import LeftSidebar from "@/components/shared/LeftSidebar";
56
import RightSidebar from "@/components/shared/RightSidebar";
@@ -18,7 +19,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
1819
<RightSidebar />
1920
</div>
2021

21-
{/* <Toaster /> */}
22+
<Toaster />
2223
</main>
2324
);
2425
};

components/forms/Answer.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import { useForm } from "react-hook-form";
99
import { zodResolver } from "@hookform/resolvers/zod";
1010
import { Editor } from "@tinymce/tinymce-react";
1111

12-
import { Button } from "@/components/ui/button";
1312
import {
1413
Form,
1514
FormField,
1615
FormItem,
1716
FormControl,
1817
FormMessage,
1918
} from "@/components/ui/form";
19+
import { Button } from "@/components/ui/button";
20+
import { toast } from "@/components/ui/use-toast";
2021

2122
import { useTheme } from "@/context/ThemeProvider";
2223

@@ -82,10 +83,22 @@ const Answer = ({
8283
editor.setContent("");
8384
}
8485
} catch (error) {
86+
toast({
87+
title: `Error ${type === "Edit" ? "editing" : "submitting"} answer ⚠️`,
88+
variant: "destructive",
89+
});
90+
8591
console.log(error);
8692
throw error;
8793
} finally {
8894
setIsSubmitting(false);
95+
96+
toast({
97+
title: `Answer ${
98+
type === "Edit" ? "edited" : "submitted"
99+
} successfully 🎉`,
100+
variant: "default",
101+
});
89102
}
90103
}
91104

@@ -114,10 +127,20 @@ const Answer = ({
114127
editor.setContent(formattedAiAnswer);
115128
}
116129
} catch (error: any) {
130+
toast({
131+
title: "Error generating AI answer ⚠️",
132+
variant: "destructive",
133+
});
134+
117135
console.log(error);
118136
throw error;
119137
} finally {
120138
setIsSubmittingAi(false);
139+
140+
toast({
141+
title: "AI answer generated successfully 🎉",
142+
variant: "default",
143+
});
121144
}
122145
};
123146

components/forms/Profile.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { Input } from "@/components/ui/input";
1919
import { Button } from "@/components/ui/button";
2020
import { Textarea } from "@/components/ui/textarea";
21+
import { toast } from "@/components/ui/use-toast";
2122

2223
import { updateUser } from "@/lib/actions/user.action";
2324
import { ProfileValidation } from "@/lib/validations";
@@ -62,9 +63,19 @@ const Profile = ({ clerkId, user }: Props) => {
6263
});
6364
router.back();
6465
} catch (error) {
66+
toast({
67+
title: "Error updating profile ⚠️",
68+
variant: "destructive",
69+
});
70+
6571
console.log(error);
6672
} finally {
6773
setIsSubmitting(false);
74+
75+
toast({
76+
title: "Profile updated successfully 🎉",
77+
variant: "default",
78+
});
6879
}
6980
}
7081

components/forms/Question.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { Input } from "@/components/ui/input";
2222
import { Button } from "@/components/ui/button";
2323
import { Badge } from "@/components/ui/badge";
24+
import { toast } from "@/components/ui/use-toast";
2425

2526
import { useTheme } from "@/context/ThemeProvider";
2627

@@ -79,9 +80,21 @@ const Question = ({ type, mongoUserId, questionDetails }: Props) => {
7980
router.push("/");
8081
}
8182
} catch (error) {
83+
toast({
84+
title: `Error ${type === "Edit" ? "editing" : "posting"} question ⚠️`,
85+
variant: "destructive",
86+
});
87+
8288
console.error(error);
8389
} finally {
8490
setIsSubmitting(false);
91+
92+
toast({
93+
title: `Question ${
94+
type === "Edit" ? "edited" : "posted"
95+
} successfully 🎉`,
96+
variant: "default",
97+
});
8598
}
8699
}
87100

components/home/HomeFilters.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { HomePageFilters } from "@/constants/filters";
1010
const HomeFilters = () => {
1111
const [active, setActive] = useState<string>("");
1212

13+
// todo: implement home filters functionality
14+
1315
return (
1416
<div className="mt-10 flex-wrap gap-3 md:flex">
1517
{HomePageFilters.map((filter) => (

components/shared/Votes.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { useEffect } from "react";
44
import Image from "next/image";
55
import { usePathname, useRouter } from "next/navigation";
66

7+
import { toast } from "@/components/ui/use-toast";
8+
79
import { upvoteAnswer, downvoteAnswer } from "@/lib/actions/answer.action";
810
import { viewQuestion } from "@/lib/actions/interaction.action";
911
import {
@@ -49,11 +51,21 @@ const Votes = ({
4951
questionId: JSON.parse(itemId),
5052
path: pathname,
5153
});
54+
55+
toast({
56+
title: `Question ${
57+
!hasSaved ? "saved" : "removed from your collection"
58+
} 🎉`,
59+
variant: !hasSaved ? "default" : "destructive",
60+
});
5261
};
5362

5463
const handleVote = async (action: string) => {
5564
if (!userId) {
56-
return;
65+
return toast({
66+
title: "Not signed in",
67+
description: "You need to be signed in to vote ⚠️",
68+
});
5769
}
5870

5971
if (action === "upvote") {
@@ -74,6 +86,11 @@ const Votes = ({
7486
path: pathname,
7587
});
7688
}
89+
90+
toast({
91+
title: `Upvote ${!hasupVoted ? "added" : "removed"} 🎉`,
92+
variant: !hasupVoted ? "default" : "destructive",
93+
});
7794
}
7895

7996
if (action === "downvote") {
@@ -94,6 +111,11 @@ const Votes = ({
94111
path: pathname,
95112
});
96113
}
114+
115+
toast({
116+
title: `Downvote ${!hasdownVoted ? "added" : "removed"} 🎉`,
117+
variant: !hasdownVoted ? "default" : "destructive",
118+
});
97119
}
98120
};
99121

components/ui/toast.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as React from "react"
2+
import * as ToastPrimitives from "@radix-ui/react-toast"
3+
import { cva, type VariantProps } from "class-variance-authority"
4+
import { X } from "lucide-react"
5+
6+
import { cn } from "@/lib/utils"
7+
8+
const ToastProvider = ToastPrimitives.Provider
9+
10+
const ToastViewport = React.forwardRef<
11+
React.ElementRef<typeof ToastPrimitives.Viewport>,
12+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
13+
>(({ className, ...props }, ref) => (
14+
<ToastPrimitives.Viewport
15+
ref={ref}
16+
className={cn(
17+
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
18+
className
19+
)}
20+
{...props}
21+
/>
22+
))
23+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24+
25+
const toastVariants = cva(
26+
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
27+
{
28+
variants: {
29+
variant: {
30+
default: "border bg-background text-foreground",
31+
destructive:
32+
"destructive group border-destructive bg-destructive text-destructive-foreground",
33+
},
34+
},
35+
defaultVariants: {
36+
variant: "default",
37+
},
38+
}
39+
)
40+
41+
const Toast = React.forwardRef<
42+
React.ElementRef<typeof ToastPrimitives.Root>,
43+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
44+
VariantProps<typeof toastVariants>
45+
>(({ className, variant, ...props }, ref) => {
46+
return (
47+
<ToastPrimitives.Root
48+
ref={ref}
49+
className={cn(toastVariants({ variant }), className)}
50+
{...props}
51+
/>
52+
)
53+
})
54+
Toast.displayName = ToastPrimitives.Root.displayName
55+
56+
const ToastAction = React.forwardRef<
57+
React.ElementRef<typeof ToastPrimitives.Action>,
58+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
59+
>(({ className, ...props }, ref) => (
60+
<ToastPrimitives.Action
61+
ref={ref}
62+
className={cn(
63+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
64+
className
65+
)}
66+
{...props}
67+
/>
68+
))
69+
ToastAction.displayName = ToastPrimitives.Action.displayName
70+
71+
const ToastClose = React.forwardRef<
72+
React.ElementRef<typeof ToastPrimitives.Close>,
73+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
74+
>(({ className, ...props }, ref) => (
75+
<ToastPrimitives.Close
76+
ref={ref}
77+
className={cn(
78+
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
79+
className
80+
)}
81+
toast-close=""
82+
{...props}
83+
>
84+
<X className="h-4 w-4" />
85+
</ToastPrimitives.Close>
86+
))
87+
ToastClose.displayName = ToastPrimitives.Close.displayName
88+
89+
const ToastTitle = React.forwardRef<
90+
React.ElementRef<typeof ToastPrimitives.Title>,
91+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
92+
>(({ className, ...props }, ref) => (
93+
<ToastPrimitives.Title
94+
ref={ref}
95+
className={cn("text-sm font-semibold", className)}
96+
{...props}
97+
/>
98+
))
99+
ToastTitle.displayName = ToastPrimitives.Title.displayName
100+
101+
const ToastDescription = React.forwardRef<
102+
React.ElementRef<typeof ToastPrimitives.Description>,
103+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
104+
>(({ className, ...props }, ref) => (
105+
<ToastPrimitives.Description
106+
ref={ref}
107+
className={cn("text-sm opacity-90", className)}
108+
{...props}
109+
/>
110+
))
111+
ToastDescription.displayName = ToastPrimitives.Description.displayName
112+
113+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
114+
115+
type ToastActionElement = React.ReactElement<typeof ToastAction>
116+
117+
export {
118+
type ToastProps,
119+
type ToastActionElement,
120+
ToastProvider,
121+
ToastViewport,
122+
Toast,
123+
ToastTitle,
124+
ToastDescription,
125+
ToastClose,
126+
ToastAction,
127+
}

components/ui/toaster.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client"
2+
3+
import {
4+
Toast,
5+
ToastClose,
6+
ToastDescription,
7+
ToastProvider,
8+
ToastTitle,
9+
ToastViewport,
10+
} from "@/components/ui/toast"
11+
import { useToast } from "@/components/ui/use-toast"
12+
13+
export function Toaster() {
14+
const { toasts } = useToast()
15+
16+
return (
17+
<ToastProvider>
18+
{toasts.map(function ({ id, title, description, action, ...props }) {
19+
return (
20+
<Toast key={id} {...props}>
21+
<div className="grid gap-1">
22+
{title && <ToastTitle>{title}</ToastTitle>}
23+
{description && (
24+
<ToastDescription>{description}</ToastDescription>
25+
)}
26+
</div>
27+
{action}
28+
<ToastClose />
29+
</Toast>
30+
)
31+
})}
32+
<ToastViewport />
33+
</ToastProvider>
34+
)
35+
}

0 commit comments

Comments
 (0)