From d435a8b608037bea5e4bd5fc84f8fad4ca91066e Mon Sep 17 00:00:00 2001 From: Ainun Nadhifah Syamsiyah Date: Wed, 13 Nov 2024 20:46:34 +0700 Subject: [PATCH] chore: add toast --- package.json | 1 + pnpm-lock.yaml | 14 ++++++++++++++ resources/js/Pages/Auth/ConfirmPassword.tsx | 8 ++++++++ resources/js/Pages/Auth/ForgotPassword.tsx | 11 ++++++++++- resources/js/Pages/Auth/Login.tsx | 8 ++++++++ resources/js/Pages/Auth/Register.tsx | 8 ++++++++ resources/js/Pages/Auth/ResetPassword.tsx | 8 ++++++++ resources/js/Pages/Auth/VerifyEmail.tsx | 11 ++++++++++- resources/js/Pages/Product/Create.tsx | 18 ++++++++++++++++++ resources/js/Pages/Product/Edit.tsx | 8 ++++++++ resources/js/Pages/Product/Show.tsx | 6 +++--- .../Pages/Profile/Partials/DeleteUserForm.tsx | 13 +++++++++---- .../Profile/Partials/UpdatePasswordForm.tsx | 12 +++++++++++- .../Partials/UpdateProfileInformationForm.tsx | 12 +++++++++++- resources/js/app.tsx | 8 +++++++- 15 files changed, 134 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 56db415..3d0c969 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "react-hook-form": "^7.53.0", "react-image-lightbox-rotation": "5.1.4-rotate", "react-select": "^5.8.1", + "sonner": "^1.7.0", "tailwind-merge": "^2.5.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26e52db..a9607bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: react-select: specifier: ^5.8.1 version: 5.8.1(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + sonner: + specifier: ^1.7.0 + version: 1.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^2.5.3 version: 2.5.3 @@ -2010,6 +2013,12 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} + sonner@1.7.0: + resolution: {integrity: sha512-W6dH7m5MujEPyug3lpI2l3TC3Pp1+LTgK0Efg+IHDrBbtEjyCmCHHo6yfNBOsf1tFZ6zf+jceWwB38baC8yO9g==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4090,6 +4099,11 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 + sonner@1.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + source-map-js@1.2.1: {} source-map@0.5.7: {} diff --git a/resources/js/Pages/Auth/ConfirmPassword.tsx b/resources/js/Pages/Auth/ConfirmPassword.tsx index 74a511f..584583a 100644 --- a/resources/js/Pages/Auth/ConfirmPassword.tsx +++ b/resources/js/Pages/Auth/ConfirmPassword.tsx @@ -3,6 +3,7 @@ import PrimaryButton from "@/Components/PrimaryButton"; import GuestLayout from "@/Layouts/GuestLayout"; import { Head, useForm as useFormInertia } from "@inertiajs/react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { password: string; @@ -28,6 +29,13 @@ export default function ConfirmPassword() { const onSubmit: SubmitHandler = () => { post(route("password.confirm"), { + onSuccess: () => toast.success("Password successfully confirmed."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error confirming password", { + description: value[0], + }); + }), onFinish: () => reset(), }); }; diff --git a/resources/js/Pages/Auth/ForgotPassword.tsx b/resources/js/Pages/Auth/ForgotPassword.tsx index 285b0e7..745d874 100644 --- a/resources/js/Pages/Auth/ForgotPassword.tsx +++ b/resources/js/Pages/Auth/ForgotPassword.tsx @@ -4,6 +4,7 @@ import GuestLayout from "@/Layouts/GuestLayout"; import { REGEX } from "@/Lib/regex"; import { Head, useForm as useFormInertia } from "@inertiajs/react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { email: string; @@ -28,7 +29,15 @@ export default function ForgotPassword({ status }: { status?: string }) { }); const onSubmit: SubmitHandler = () => { - post(route("password.email")); + post(route("password.email"), { + onSuccess: () => toast.success("Password reset link sent to your email."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error sending password reset link", { + description: value[0], + }); + }), + }); }; return ( diff --git a/resources/js/Pages/Auth/Login.tsx b/resources/js/Pages/Auth/Login.tsx index a82fd5b..3f8d1c0 100644 --- a/resources/js/Pages/Auth/Login.tsx +++ b/resources/js/Pages/Auth/Login.tsx @@ -4,6 +4,7 @@ import PrimaryButton from "@/Components/PrimaryButton"; import GuestLayout from "@/Layouts/GuestLayout"; import { Head, Link, useForm as useFormInertia } from "@inertiajs/react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { email: string; @@ -41,6 +42,13 @@ export default function Login({ const onSubmit: SubmitHandler = () => { post(route("login"), { + onSuccess: () => toast.success("You have been successfully logged in."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error logging in", { + description: value[0], + }); + }), onFinish: () => reset(), }); }; diff --git a/resources/js/Pages/Auth/Register.tsx b/resources/js/Pages/Auth/Register.tsx index 1e03c06..289f0d7 100644 --- a/resources/js/Pages/Auth/Register.tsx +++ b/resources/js/Pages/Auth/Register.tsx @@ -4,6 +4,7 @@ import GuestLayout from "@/Layouts/GuestLayout"; import { REGEX } from "@/Lib/regex"; import { Head, Link, useForm as useFormInertia } from "@inertiajs/react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { name: string; @@ -39,6 +40,13 @@ export default function Register() { const onSubmit: SubmitHandler = () => { post(route("register"), { + onSuccess: () => toast.success("Your account has been created."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error creating account", { + description: value[0], + }); + }), onFinish: () => reset(), }); }; diff --git a/resources/js/Pages/Auth/ResetPassword.tsx b/resources/js/Pages/Auth/ResetPassword.tsx index b8953ae..a1f0252 100644 --- a/resources/js/Pages/Auth/ResetPassword.tsx +++ b/resources/js/Pages/Auth/ResetPassword.tsx @@ -4,6 +4,7 @@ import GuestLayout from "@/Layouts/GuestLayout"; import { REGEX } from "@/Lib/regex"; import { Head, useForm as useFormInertia } from "@inertiajs/react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { token: string; @@ -51,6 +52,13 @@ export default function ResetPassword({ const onSubmit: SubmitHandler = () => { post(route("password.store"), { + onSuccess: () => toast.success("Password successfully reset."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error resetting password", { + description: value[0], + }); + }), onFinish: () => reset(), }); }; diff --git a/resources/js/Pages/Auth/VerifyEmail.tsx b/resources/js/Pages/Auth/VerifyEmail.tsx index fe19766..1af29cb 100644 --- a/resources/js/Pages/Auth/VerifyEmail.tsx +++ b/resources/js/Pages/Auth/VerifyEmail.tsx @@ -2,6 +2,7 @@ import PrimaryButton from "@/Components/PrimaryButton"; import GuestLayout from "@/Layouts/GuestLayout"; import { Head, Link, useForm } from "@inertiajs/react"; import type { FormEventHandler } from "react"; +import { toast } from "sonner"; export default function VerifyEmail({ status }: { status?: string }) { const { post, processing } = useForm({}); @@ -9,7 +10,15 @@ export default function VerifyEmail({ status }: { status?: string }) { const submit: FormEventHandler = (e) => { e.preventDefault(); - post(route("verification.send")); + post(route("verification.send"), { + onSuccess: () => toast.success("Verification email sent."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error sending verification email", { + description: value[0], + }); + }), + }); }; return ( diff --git a/resources/js/Pages/Product/Create.tsx b/resources/js/Pages/Product/Create.tsx index 8a6dcb2..94cb1db 100644 --- a/resources/js/Pages/Product/Create.tsx +++ b/resources/js/Pages/Product/Create.tsx @@ -12,6 +12,7 @@ import axios from "axios"; import { ArrowLeft } from "lucide-react"; import * as React from "react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { name: string; @@ -55,6 +56,9 @@ export default function Create({ if (!isDropped || !image_url) return; setIsLoading(true); + toast.loading("Detecting image category...", { + id: "detecting-image-category", + }); try { const response = await axios({ @@ -74,9 +78,13 @@ export default function Create({ ); setValue("categories", newCategories); clearErrors("categories"); + + toast.success("Image category detected"); } catch (error) { + toast.error("Error detecting image category"); console.error(error); } finally { + toast.dismiss("detecting-image-category"); setIsLoading(false); } }; @@ -92,6 +100,16 @@ export default function Create({ const onSubmit: SubmitHandler = () => { post(route("product.store"), { forceFormData: true, + onSuccess: () => { + toast.success("Product has been added successfully"); + }, + onError: (errors) => { + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error adding product", { + description: value[0], + }); + }); + }, onFinish: () => reset(), }); }; diff --git a/resources/js/Pages/Product/Edit.tsx b/resources/js/Pages/Product/Edit.tsx index 01f5198..e22e74b 100644 --- a/resources/js/Pages/Product/Edit.tsx +++ b/resources/js/Pages/Product/Edit.tsx @@ -8,6 +8,7 @@ import { ProductType } from "@/types/entities/product"; import { Head, useForm as useFormInertia } from "@inertiajs/react"; import { ArrowLeft } from "lucide-react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { name: string; @@ -59,6 +60,13 @@ export default function Edit({ post(route("product.update", product.id), { method: "patch", forceFormData: true, + onSuccess: () => toast.success("Product has been updated."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error updating product", { + description: value[0], + }); + }), onFinish: () => reset(), }); }; diff --git a/resources/js/Pages/Product/Show.tsx b/resources/js/Pages/Product/Show.tsx index 19396d2..696bd0f 100644 --- a/resources/js/Pages/Product/Show.tsx +++ b/resources/js/Pages/Product/Show.tsx @@ -8,6 +8,7 @@ import { Inertia } from "@inertiajs/inertia"; import { Head, useForm as useFormInertia } from "@inertiajs/react"; import { ArrowLeft } from "lucide-react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { product_id: string; @@ -18,9 +19,8 @@ export default function Show({ product }: { product: ProductType }) { const handleDelete = () => { if (confirm("Are you sure you want to delete this product?")) { Inertia.delete(`/product/${product.id}/delete`, { - onSuccess: () => { - alert("Product deleted successfully!"); - }, + onSuccess: () => toast.success("Product has been deleted."), + onError: () => toast.error("Error deleting product."), }); } }; diff --git a/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx b/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx index 74cc473..8d40bd6 100644 --- a/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx +++ b/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx @@ -5,6 +5,7 @@ import SecondaryButton from "@/Components/SecondaryButton"; import { useForm as useFormInertia } from "@inertiajs/react"; import { useState } from "react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { passsword: string; @@ -45,19 +46,23 @@ export default function DeleteUserForm({ const onSubmit: SubmitHandler = () => { destroy(route("profile.destroy"), { preserveScroll: true, - onSuccess: () => closeModal(), - onError: () => + onSuccess: () => { + toast.success("Your account has been successfully deleted."); + closeModal(); + }, + onError: () => { + toast.error("An error occurred while deleting your account."); setError("passsword", { type: "manual", message: "The provided password was incorrect.", - }), + }); + }, onFinish: () => reset(), }); }; const closeModal = () => { setConfirmingUserDeletion(false); - reset(); }; diff --git a/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx b/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx index b7e90d7..cee9084 100644 --- a/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx +++ b/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx @@ -4,6 +4,7 @@ import { REGEX } from "@/Lib/regex"; import { Transition } from "@headlessui/react"; import { useForm as useFormInertia } from "@inertiajs/react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { current_password: string; @@ -41,7 +42,16 @@ export default function UpdatePasswordForm({ const onSubmit: SubmitHandler = () => { put(route("password.update"), { preserveScroll: true, - onSuccess: () => reset(), + onSuccess: () => { + toast.success("Password successfully updated."); + reset(); + }, + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error updating password", { + description: value[0], + }); + }), }); }; diff --git a/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx b/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx index 3e35a3a..ab2156e 100644 --- a/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +++ b/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx @@ -4,6 +4,7 @@ import { REGEX } from "@/Lib/regex"; import { Transition } from "@headlessui/react"; import { Link, useForm as useFormInertia, usePage } from "@inertiajs/react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { toast } from "sonner"; type FormData = { name: string; @@ -55,7 +56,16 @@ export default function UpdateProfileInformation({ }); const onSubmit: SubmitHandler = () => { - patch(route("profile.update")); + patch(route("profile.update"), { + onSuccess: () => + toast.success("Profile information successfully updated."), + onError: (errors) => + Object.entries(errors).forEach(([_, value]) => { + toast.error("Error updating profile information", { + description: value[0], + }); + }), + }); }; return ( diff --git a/resources/js/app.tsx b/resources/js/app.tsx index c0381c7..1ff4e3b 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -4,6 +4,7 @@ import "./bootstrap"; import { createInertiaApp } from "@inertiajs/react"; import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers"; import { createRoot } from "react-dom/client"; +import { Toaster } from "sonner"; const appName = import.meta.env.VITE_APP_NAME || "Laravel"; @@ -25,7 +26,12 @@ createInertiaApp({ setup({ el, App, props }) { const root = createRoot(el); - root.render(); + root.render( + <> + + + , + ); }, progress: { color: "#4B5563",