diff --git a/src/components/generic/Breadcrumb/AppBreadcrumb.jsx b/src/components/generic/Breadcrumb/AppBreadcrumb.jsx index 8d5072f..5103bdc 100644 --- a/src/components/generic/Breadcrumb/AppBreadcrumb.jsx +++ b/src/components/generic/Breadcrumb/AppBreadcrumb.jsx @@ -82,7 +82,8 @@ const AppBreadcrumb = ({ items = [], color = {} }) => { {isLast ? ( {item.icon} - {item.label} + {item.label} + ... ) : ( diff --git a/src/components/generic/UserMenu.jsx b/src/components/generic/UserMenu.jsx index 2686a75..18b146b 100644 --- a/src/components/generic/UserMenu.jsx +++ b/src/components/generic/UserMenu.jsx @@ -186,7 +186,6 @@ function SignOutOverlay({ open }) { // ─── Main UserMenu ──────────────────────────────────────────────────────────── export default function UserMenu() { const { user, logout } = useAuth() - const { setTheme } = useTheme() const { avatarUrl } = useProfile() const navigate = useNavigate() @@ -208,7 +207,6 @@ export default function UserMenu() { const handleLogout = async () => { setSigningOut(true) await logout() - setTheme('light') navigate('/login', { replace: true }) } diff --git a/src/components/ui/sonner.jsx b/src/components/ui/sonner.jsx index ec11e18..8b203ad 100644 --- a/src/components/ui/sonner.jsx +++ b/src/components/ui/sonner.jsx @@ -11,6 +11,7 @@ const Toaster = ({ @@ -33,7 +34,10 @@ const Toaster = ({ "--normal-bg": "var(--popover)", "--normal-text": "var(--popover-foreground)", "--normal-border": "var(--border)", - "--border-radius": "var(--radius)" + "--border-radius": "var(--radius)", + "--toast-close-button-start": "unset", + "--toast-close-button-end": "0", + "--toast-close-button-transform": "translate(35%, -35%)" } } toastOptions={{ diff --git a/src/contexts/AdminAchievementsContext.jsx b/src/contexts/AdminAchievementsContext.jsx index 9b45bba..aea9007 100644 --- a/src/contexts/AdminAchievementsContext.jsx +++ b/src/contexts/AdminAchievementsContext.jsx @@ -19,12 +19,7 @@ export function AdminAchievementsProvider({ children }) { setLoading(true); try { return await fn(); } catch (err) { - toast(err?.response?.data?.message ?? "Something went wrong.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Something went wrong."); return null; } finally { setLoading(false); } }, []); @@ -46,12 +41,7 @@ export function AdminAchievementsProvider({ children }) { const createAchievement = useCallback((payload) => request(async () => { const { data } = await api.post("/admin/achievements", payload); - toast("Achievement created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Achievement created."); return data.data; }), [request]); @@ -62,12 +52,7 @@ export function AdminAchievementsProvider({ children }) { prev.map((a) => (String(a.achievement_definition_id) === String(id) ? data.data : a)) ); if (achievement && String(achievement.achievement_definition_id) === String(id)) setAchievement(data.data); - toast("Achievement updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Achievement updated."); return data.data; }), [request, achievement]); @@ -75,12 +60,7 @@ export function AdminAchievementsProvider({ children }) { request(async () => { await api.delete(`/admin/achievements/${id}`); setAchievements((prev) => prev.filter((a) => String(a.achievement_definition_id) !== String(id))); - toast("Achievement deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Achievement deleted."); return true; }), [request]); diff --git a/src/contexts/AdminAdvertisementContext.jsx b/src/contexts/AdminAdvertisementContext.jsx index e04835c..1a6bbb1 100644 --- a/src/contexts/AdminAdvertisementContext.jsx +++ b/src/contexts/AdminAdvertisementContext.jsx @@ -34,12 +34,7 @@ export function AdvertisementsProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -105,12 +100,7 @@ export function AdvertisementsProvider({ children }) { const advertisement = res.data?.data?.data ?? null; if (advertisement) { setAdvertisements((prev) => [advertisement, ...prev]); - toast("Advertisement created successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Advertisement created successfully."); } return res.data; }), @@ -126,12 +116,7 @@ export function AdvertisementsProvider({ children }) { if (advertisement) { setAdvertisements((prev) => prev.map((a) => (a.advertisement_id === advertisementId ? advertisement : a))); setSelectedAdvertisement(advertisement); - toast("Advertisement updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Advertisement updated successfully."); } return res.data; }), @@ -147,12 +132,7 @@ export function AdvertisementsProvider({ children }) { }); setAdvertisements((prev) => prev.filter((a) => a.advertisement_id !== advertisementId)); setSelectedAdvertisement((prev) => (prev?.advertisement_id === advertisementId ? null : prev)); - toast("Advertisement archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Advertisement archived."); return res.data; }), [request] @@ -166,12 +146,7 @@ export function AdvertisementsProvider({ children }) { data: { ids, deletedBy }, }); setAdvertisements((prev) => prev.filter((a) => !ids.includes(a.advertisement_id))); - toast(`${ids.length} advertisement(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} advertisement(s) archived.`); return res.data; }), [request] @@ -185,12 +160,7 @@ export function AdvertisementsProvider({ children }) { const advertisement = res.data?.data?.data ?? null; if (advertisement) { setAdvertisements((prev) => prev.filter((a) => a.advertisement_id !== advertisementId)); - toast("Advertisement restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Advertisement restored."); } return res.data; }), @@ -203,12 +173,7 @@ export function AdvertisementsProvider({ children }) { request(async () => { const res = await api.patch("/admin/advertisements/bulk-restore", { ids }); setAdvertisements((prev) => prev.filter((a) => !ids.includes(a.advertisement_id))); - toast(`${ids.length} advertisement(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} advertisement(s) restored.`); return res.data; }), [request] @@ -220,12 +185,7 @@ export function AdvertisementsProvider({ children }) { request(async () => { const res = await api.delete(`/admin/advertisements/${advertisementId}/permanent`); setAdvertisements((prev) => prev.filter((a) => a.advertisement_id !== advertisementId)); - toast("Advertisement permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Advertisement permanently deleted."); return res.data; }), [request] @@ -237,12 +197,7 @@ export function AdvertisementsProvider({ children }) { request(async () => { const res = await api.delete("/admin/advertisements/bulk/permanent", { data: { ids } }); setAdvertisements((prev) => prev.filter((a) => !ids.includes(a.advertisement_id))); - toast(`${ids.length} advertisement(s) permanently deleted.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} advertisement(s) permanently deleted.`); return res.data; }), [request] diff --git a/src/contexts/AdminAssetsContext.jsx b/src/contexts/AdminAssetsContext.jsx index 3b7affd..2a12451 100644 --- a/src/contexts/AdminAssetsContext.jsx +++ b/src/contexts/AdminAssetsContext.jsx @@ -69,12 +69,7 @@ export function AssetsProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -206,12 +201,7 @@ export function AssetsProvider({ children }) { if (asset) { setAssets((prev) => [asset, ...prev]); invalidateListCache(); - toast("Asset uploaded successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Asset uploaded successfully."); } return res.data; }), @@ -237,12 +227,7 @@ export function AssetsProvider({ children }) { setAssets((prev) => prev.map((a) => (a.asset_id === assetId ? asset : a))); setSelectedAsset(asset); invalidateListCache(); - toast("Asset updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Asset updated successfully."); } return res.data; }), @@ -259,12 +244,7 @@ export function AssetsProvider({ children }) { setAssets((prev) => prev.filter((a) => a.asset_id !== assetId)); setSelectedAsset((prev) => (prev?.asset_id === assetId ? null : prev)); invalidateListCache(); - toast("Asset archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Asset archived."); return res.data; }), [request] @@ -279,12 +259,7 @@ export function AssetsProvider({ children }) { }); setAssets((prev) => prev.filter((a) => !ids.includes(a.asset_id))); invalidateListCache(); - toast(`${ids.length} asset(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} asset(s) archived.`); return res.data; }), [request] @@ -299,12 +274,7 @@ export function AssetsProvider({ children }) { if (asset) { setAssets((prev) => prev.filter((a) => a.asset_id !== assetId)); invalidateListCache(); - toast("Asset restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Asset restored."); } return res.data; }), @@ -318,12 +288,7 @@ export function AssetsProvider({ children }) { const res = await api.patch("/admin/assets/bulk-restore", { ids }); setAssets((prev) => prev.filter((a) => !ids.includes(a.asset_id))); invalidateListCache(); - toast(`${ids.length} asset(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} asset(s) restored.`); return res.data; }), [request] @@ -337,12 +302,7 @@ export function AssetsProvider({ children }) { setAssets((prev) => prev.filter((a) => a.asset_id !== assetId)); setSelectedAsset((prev) => (prev?.asset_id === assetId ? null : prev)); invalidateListCache(); - toast("Asset permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Asset permanently deleted."); return res.data; }), [request] @@ -357,12 +317,7 @@ export function AssetsProvider({ children }) { }); setAssets((prev) => prev.filter((a) => !ids.includes(a.asset_id))); invalidateListCache(); - toast(`${ids.length} asset(s) permanently deleted.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} asset(s) permanently deleted.`); return res.data; }), [request] diff --git a/src/contexts/AdminCategoriesContext.jsx b/src/contexts/AdminCategoriesContext.jsx index 5f54c9d..cd4b88b 100644 --- a/src/contexts/AdminCategoriesContext.jsx +++ b/src/contexts/AdminCategoriesContext.jsx @@ -13,12 +13,7 @@ export function AdminCategoriesProvider({ children }) { setLoading(true); try { return await fn(); } catch (err) { - toast(err?.response?.data?.message ?? "Something went wrong.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Something went wrong."); return null; } finally { setLoading(false); } }, []); @@ -37,48 +32,28 @@ export function AdminCategoriesProvider({ children }) { const createCategory = useCallback((payload) => wrap(async () => { const { data } = await api.post("/admin/categories", payload); - toast("Category created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Category created."); return data.data; }), [wrap]); const updateCategory = useCallback((id, payload) => wrap(async () => { const { data } = await api.put(`/admin/categories/${id}`, payload); setCategory(data.data ?? null); - toast("Category updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Category updated."); return data.data; }), [wrap]); const archiveCategory = useCallback((id) => wrap(async () => { await api.delete(`/admin/categories/${id}`); setCategories((prev) => prev.filter((c) => c.id !== id)); - toast("Category archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Category archived."); return true; }), [wrap]); const restoreCategory = useCallback((id) => wrap(async () => { await api.post(`/admin/categories/${id}/restore`); setCategories((prev) => prev.filter((c) => c.id !== id)); - toast("Category restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Category restored."); return true; }), [wrap]); diff --git a/src/contexts/AdminCourseReadingProgressContext.jsx b/src/contexts/AdminCourseReadingProgressContext.jsx index 1785c59..e757bef 100644 --- a/src/contexts/AdminCourseReadingProgressContext.jsx +++ b/src/contexts/AdminCourseReadingProgressContext.jsx @@ -43,12 +43,7 @@ export function AdminCourseReadingProgressProvider({ children }) { setProgressList(data.data ?? []); setDetailCache({}); } catch (err) { - toast(err?.response?.data?.message ?? 'Could not load reading progress.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? 'Could not load reading progress.'); } finally { setListLoading(false); } @@ -65,12 +60,7 @@ export function AdminCourseReadingProgressProvider({ children }) { setDetailCache((prev) => ({ ...prev, [userId]: breakdown })); return breakdown; } catch (err) { - toast(err?.response?.data?.message ?? 'Could not load user progress.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? 'Could not load user progress.'); return null; } finally { setDetailLoading(false); diff --git a/src/contexts/AdminCoursesContext.jsx b/src/contexts/AdminCoursesContext.jsx index 236a906..cee9906 100644 --- a/src/contexts/AdminCoursesContext.jsx +++ b/src/contexts/AdminCoursesContext.jsx @@ -52,18 +52,8 @@ export function CoursesProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; - if (err.status === 404 && message) toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); - else toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + if (err.status === 404 && message) toast(message); + else toast(message); return null; } finally { setLoading(false); @@ -121,12 +111,7 @@ export function CoursesProvider({ children }) { const course = data?.data?.data ?? null; if (course) { setCourses((prev) => [course, ...prev]); - toast("Course created successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Course created successfully."); } return data; }), @@ -141,12 +126,7 @@ export function CoursesProvider({ children }) { if (course) { setCourses((prev) => prev.map((c) => (c.course_id === courseId ? course : c))); setCourse(course); - toast("Course updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Course updated successfully."); } return data; }), @@ -159,12 +139,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}`); setCourses((prev) => prev.filter((c) => c.course_id !== courseId)); setCourse((prev) => (prev?.course_id === courseId ? null : prev)); - toast("Course archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Course archived."); return data; }), [request], @@ -175,12 +150,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/bulk`, { data: { ids } }); setCourses((prev) => prev.filter((c) => !ids.includes(c.course_id))); - toast("Courses archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Courses archived."); return data; }), [request], @@ -211,12 +181,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setCourses((prev) => prev.filter((c) => c.course_id !== courseId)); - toast("Course restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Course restored."); } return data; }), @@ -228,12 +193,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.patch(`${BASE}/restore/bulk`, { ids }); setCourses((prev) => prev.filter((c) => !ids.includes(c.course_id))); - toast("Courses restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Courses restored."); return data; }), [request], @@ -245,12 +205,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}/permanent`); setCourses((prev) => prev.filter((c) => c.course_id !== courseId)); setCourse((prev) => (prev?.course_id === courseId ? null : prev)); - toast("Course permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Course permanently deleted."); return data; }), [request], @@ -261,12 +216,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/bulk/permanent`, { data: { ids } }); setCourses((prev) => prev.filter((c) => !ids.includes(c.course_id))); - toast("Courses permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Courses permanently deleted."); return data; }), [request], @@ -305,12 +255,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.put(`${BASE}/${courseId}/prerequisites`, { prerequisites }); setPrerequisites(prerequisites); - toast("Prerequisites updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Prerequisites updated."); return data; }), [request], @@ -360,12 +305,7 @@ export function CoursesProvider({ children }) { const unit = data?.data?.data ?? null; if (unit) { setUnits((prev) => [...prev, unit]); - toast("Unit created successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Unit created successfully."); } return data; }), @@ -380,12 +320,7 @@ export function CoursesProvider({ children }) { if (unit) { setUnits((prev) => prev.map((u) => (u.unit_id === unitId ? unit : u))); setUnit(unit); - toast("Unit updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Unit updated successfully."); } return data; }), @@ -398,12 +333,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}`); setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); setUnit((prev) => (prev?.unit_id === unitId ? null : prev)); - toast("Unit archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Unit archived."); return data; }), [request], @@ -414,12 +344,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/bulk`, { data: { ids } }); setUnits((prev) => prev.filter((u) => !ids.includes(u.unit_id))); - toast("Units archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Units archived."); return data; }), [request], @@ -451,12 +376,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); - toast("Unit restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Unit restored."); } return data; }), @@ -468,12 +388,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/restore/bulk`, { ids } ); setUnits((prev) => prev.filter((u) => !ids.includes(u.unit_id))); - toast("Units restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Units restored."); return data; }), [request], @@ -485,12 +400,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/permanent`); setUnits((prev) => prev.filter((u) => u.unit_id !== unitId)); setUnit((prev) => (prev?.unit_id === unitId ? null : prev)); - toast("Unit permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Unit permanently deleted."); return data; }), [request], @@ -501,12 +411,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/bulk/permanent`, { data: { ids } }); setUnits((prev) => prev.filter((u) => !ids.includes(u.unit_id))); - toast("Units permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Units permanently deleted."); return data; }), [request], @@ -545,12 +450,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setQuiz(result); - toast("Quiz created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Quiz created."); } return data; }), @@ -564,12 +464,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setQuiz(result); - toast("Quiz updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Quiz updated."); } return data; }), @@ -582,12 +477,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}`, { data: { deletedBy } }); setQuiz(null); setQuestions([]); - toast("Quiz archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Quiz archived."); return data; }), [request], @@ -613,12 +503,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setQuiz(result); - toast("Quiz restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Quiz restored."); } return data; }), @@ -647,12 +532,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => [...prev, result]); - toast("Question added.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question added."); } return data; }), @@ -666,12 +546,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => prev.map((q) => (q.question_id === questionId ? result : q))); - toast("Question updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question updated."); } return data; }), @@ -684,12 +559,7 @@ export function CoursesProvider({ children }) { const { data } = await api.put(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/bulk-sync`, { questions, updatedBy }); const result = data?.data?.data ?? []; setQuestions(result); - toast("Quiz saved.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Quiz saved."); return data; }), [request], @@ -700,12 +570,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/${questionId}`, { data: { deletedBy } }); setQuestions((prev) => prev.filter((q) => q.question_id !== questionId)); - toast("Question archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question archived."); return data; }), [request], @@ -716,12 +581,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/bulk`, { data: { ids, deletedBy } }); setQuestions((prev) => prev.filter((q) => !ids.includes(q.question_id))); - toast("Questions archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Questions archived."); return data; }), [request], @@ -742,12 +602,7 @@ export function CoursesProvider({ children }) { (courseId, unitId, quizId, questionId, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/${questionId}/restore`, { restoredBy }); - toast("Question restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question restored."); return data; }), [request], @@ -757,12 +612,7 @@ export function CoursesProvider({ children }) { (courseId, unitId, quizId, ids, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/quiz/${quizId}/questions/restore/bulk`, { ids, restoredBy }); - toast("Questions restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Questions restored."); return data; }), [request], @@ -797,12 +647,7 @@ export function CoursesProvider({ children }) { const lesson = data?.data?.data ?? null; if (lesson) { setLessons((prev) => [...prev, lesson]); - toast("Lesson created successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lesson created successfully."); } return data; }), @@ -817,12 +662,7 @@ export function CoursesProvider({ children }) { if (lesson) { setLessons((prev) => prev.map((l) => (l.lesson_id === lessonId ? lesson : l))); setLesson(lesson); - toast("Lesson updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lesson updated successfully."); } return data; }), @@ -835,12 +675,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}`); setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); setLesson((prev) => (prev?.lesson_id === lessonId ? null : prev)); - toast("Lesson archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lesson archived."); return data; }), [request], @@ -851,12 +686,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/bulk`, { data: { ids } }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); - toast("Lessons archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lessons archived."); return data; }), [request], @@ -888,12 +718,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); - toast("Lesson restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lesson restored."); } return data; }), @@ -905,12 +730,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/units/${unitId}/lessons/restore/bulk`, { ids }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); - toast("Lessons restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lessons restored."); return data; }), [request], @@ -922,12 +742,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/${lessonId}/permanent`); setLessons((prev) => prev.filter((l) => l.lesson_id !== lessonId)); setLesson((prev) => (prev?.lesson_id === lessonId ? null : prev)); - toast("Lesson permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lesson permanently deleted."); return data; }), [request], @@ -938,12 +753,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/units/${unitId}/lessons/bulk/permanent`, { data: { ids } }); setLessons((prev) => prev.filter((l) => !ids.includes(l.lesson_id))); - toast("Lessons permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lessons permanently deleted."); return data; }), [request], @@ -971,12 +781,7 @@ export function CoursesProvider({ children }) { const page = data?.data?.data ?? null; if (page) { setLessonPage(page); - toast("Lesson page saved.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Lesson page saved."); } return data; }), @@ -1006,12 +811,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setAssessment(result); - toast("Assessment created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Assessment created."); } return data; }), @@ -1025,12 +825,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setAssessment(result); - toast("Assessment updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Assessment updated."); } return data; }), @@ -1043,12 +838,7 @@ export function CoursesProvider({ children }) { const { data } = await api.delete(`${BASE}/${courseId}/assessment/${assessmentId}`, { data: { deletedBy } }); setAssessment(null); setQuestions([]); - toast("Assessment archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Assessment archived."); return data; }), [request], @@ -1074,12 +864,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setAssessment(result); - toast("Assessment restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Assessment restored."); } return data; }), @@ -1108,12 +893,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => [...prev, result]); - toast("Question added.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question added."); } return data; }), @@ -1127,12 +907,7 @@ export function CoursesProvider({ children }) { const result = data?.data?.data ?? null; if (result) { setQuestions((prev) => prev.map((q) => (q.question_id === questionId ? result : q))); - toast("Question updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question updated."); } return data; }), @@ -1145,12 +920,7 @@ export function CoursesProvider({ children }) { const { data } = await api.put(`${BASE}/${courseId}/assessment/${assessmentId}/questions/bulk-sync`, { questions, updatedBy }); const result = data?.data?.data ?? []; setQuestions(result); - toast("Assessment saved.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Assessment saved."); return data; }), [request], @@ -1161,12 +931,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/assessment/${assessmentId}/questions/${questionId}`, { data: { deletedBy } }); setQuestions((prev) => prev.filter((q) => q.question_id !== questionId)); - toast("Question archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question archived."); return data; }), [request], @@ -1177,12 +942,7 @@ export function CoursesProvider({ children }) { request(async () => { const { data } = await api.delete(`${BASE}/${courseId}/assessment/${assessmentId}/questions/bulk`, { data: { ids, deletedBy } }); setQuestions((prev) => prev.filter((q) => !ids.includes(q.question_id))); - toast("Questions archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Questions archived."); return data; }), [request], @@ -1203,12 +963,7 @@ export function CoursesProvider({ children }) { (courseId, assessmentId, questionId, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/assessment/${assessmentId}/questions/${questionId}/restore`, { restoredBy }); - toast("Question restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Question restored."); return data; }), [request], @@ -1218,12 +973,7 @@ export function CoursesProvider({ children }) { (courseId, assessmentId, ids, restoredBy) => request(async () => { const { data } = await api.patch(`${BASE}/${courseId}/assessment/${assessmentId}/questions/restore/bulk`, { ids, restoredBy }); - toast("Questions restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Questions restored."); return data; }), [request], @@ -1280,12 +1030,7 @@ export function CoursesProvider({ children }) { const saveCourseProduct = useCallback( (courseId, payload) => request(async () => { const { data } = await api.put(`/admin/products/courses/${courseId}/product`, payload); - toast("Product listing saved.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Product listing saved."); return data.data ?? null; }), [request], ); @@ -1293,12 +1038,7 @@ export function CoursesProvider({ children }) { const removeCourseProduct = useCallback( (courseId) => request(async () => { await api.delete(`/admin/products/courses/${courseId}/product`); - toast("Product listing removed.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Product listing removed."); return true; }), [request], ); @@ -1313,12 +1053,7 @@ export function CoursesProvider({ children }) { const syncCourseCategories = useCallback( (courseId, categoryIds) => request(async () => { const { data } = await api.post(`/admin/products/courses/${courseId}/categories`, { category_ids: categoryIds }); - toast("Categories updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Categories updated."); return data.data ?? []; }), [request], ); @@ -1333,12 +1068,7 @@ export function CoursesProvider({ children }) { const syncInstructors = useCallback( (courseId, instructors) => request(async () => { await api.put(`${BASE}/${courseId}/instructors`, { instructors }); - toast("Instructors updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Instructors updated."); }), [request], ); @@ -1352,12 +1082,7 @@ export function CoursesProvider({ children }) { const syncCourseAchievements = useCallback( (courseId, achievement_keys) => request(async () => { await api.put(`${BASE}/${courseId}/achievements`, { achievement_keys }); - toast("Rewards updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Rewards updated."); }), [request], ); diff --git a/src/contexts/AdminDashboardContext.jsx b/src/contexts/AdminDashboardContext.jsx index efdabc6..33b1185 100644 --- a/src/contexts/AdminDashboardContext.jsx +++ b/src/contexts/AdminDashboardContext.jsx @@ -21,12 +21,7 @@ export function AdminDashboardProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); diff --git a/src/contexts/AdminNotificationBroadcastContext.jsx b/src/contexts/AdminNotificationBroadcastContext.jsx index 12df74d..83f5cac 100644 --- a/src/contexts/AdminNotificationBroadcastContext.jsx +++ b/src/contexts/AdminNotificationBroadcastContext.jsx @@ -34,12 +34,7 @@ export function NotificationBroadcastsProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -105,12 +100,7 @@ export function NotificationBroadcastsProvider({ children }) { const broadcast = res.data?.data?.data ?? null; if (broadcast) { setBroadcasts((prev) => [broadcast, ...prev]); - toast("Notification broadcast created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Notification broadcast created."); } return res.data; }), @@ -126,12 +116,7 @@ export function NotificationBroadcastsProvider({ children }) { if (broadcast) { setBroadcasts((prev) => prev.map((b) => (b.broadcast_id === broadcastId ? broadcast : b))); setSelectedBroadcast(broadcast); - toast("Notification broadcast updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Notification broadcast updated."); } return res.data; }), @@ -147,12 +132,7 @@ export function NotificationBroadcastsProvider({ children }) { if (broadcast) { setBroadcasts((prev) => prev.map((b) => (b.broadcast_id === broadcastId ? broadcast : b))); setSelectedBroadcast(broadcast); - toast("Notification broadcast sent.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Notification broadcast sent."); } return res.data; }), @@ -168,12 +148,7 @@ export function NotificationBroadcastsProvider({ children }) { }); setBroadcasts((prev) => prev.filter((b) => b.broadcast_id !== broadcastId)); setSelectedBroadcast((prev) => (prev?.broadcast_id === broadcastId ? null : prev)); - toast("Notification broadcast archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Notification broadcast archived."); return res.data; }), [request] @@ -187,12 +162,7 @@ export function NotificationBroadcastsProvider({ children }) { data: { ids, deletedBy }, }); setBroadcasts((prev) => prev.filter((b) => !ids.includes(b.broadcast_id))); - toast(`${ids.length} notification broadcast(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} notification broadcast(s) archived.`); return res.data; }), [request] @@ -206,12 +176,7 @@ export function NotificationBroadcastsProvider({ children }) { const broadcast = res.data?.data?.data ?? null; if (broadcast) { setBroadcasts((prev) => prev.filter((b) => b.broadcast_id !== broadcastId)); - toast("Notification broadcast restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Notification broadcast restored."); } return res.data; }), @@ -224,12 +189,7 @@ export function NotificationBroadcastsProvider({ children }) { request(async () => { const res = await api.patch("/admin/notification-broadcasts/bulk-restore", { ids }); setBroadcasts((prev) => prev.filter((b) => !ids.includes(b.broadcast_id))); - toast(`${ids.length} notification broadcast(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} notification broadcast(s) restored.`); return res.data; }), [request] @@ -241,12 +201,7 @@ export function NotificationBroadcastsProvider({ children }) { request(async () => { const res = await api.delete(`/admin/notification-broadcasts/${broadcastId}/permanent`); setBroadcasts((prev) => prev.filter((b) => b.broadcast_id !== broadcastId)); - toast("Notification broadcast permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Notification broadcast permanently deleted."); return res.data; }), [request] @@ -258,12 +213,7 @@ export function NotificationBroadcastsProvider({ children }) { request(async () => { const res = await api.delete("/admin/notification-broadcasts/bulk/permanent", { data: { ids } }); setBroadcasts((prev) => prev.filter((b) => !ids.includes(b.broadcast_id))); - toast(`${ids.length} notification broadcast(s) permanently deleted.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} notification broadcast(s) permanently deleted.`); return res.data; }), [request] diff --git a/src/contexts/AdminNotificationTemplateContext.jsx b/src/contexts/AdminNotificationTemplateContext.jsx index b13efc8..ef3d3c0 100644 --- a/src/contexts/AdminNotificationTemplateContext.jsx +++ b/src/contexts/AdminNotificationTemplateContext.jsx @@ -19,12 +19,7 @@ export function AdminNotificationTemplateProvider({ children }) { setLoading(true); try { return await fn(); } catch (err) { - toast(err?.response?.data?.message ?? "Something went wrong.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Something went wrong."); return null; } finally { setLoading(false); } }, []); @@ -50,12 +45,7 @@ export function AdminNotificationTemplateProvider({ children }) { prev.map((t) => (String(t.notification_template_id) === String(id) ? data.data : t)) ); if (template && String(template.notification_template_id) === String(id)) setTemplate(data.data); - toast("Notification template updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Notification template updated."); return data.data; }), [request, template]); diff --git a/src/contexts/AdminTaskContext.jsx b/src/contexts/AdminTaskContext.jsx index 848b77f..58eab61 100644 --- a/src/contexts/AdminTaskContext.jsx +++ b/src/contexts/AdminTaskContext.jsx @@ -57,12 +57,7 @@ export function AdminTaskProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? 'Something went wrong.'; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -75,12 +70,7 @@ export function AdminTaskProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? 'Something went wrong.'; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setCompletionLoading(false); @@ -143,12 +133,7 @@ export function AdminTaskProvider({ children }) { (payload) => request(async () => { const res = await api.post(BASE, payload); - toast('Task list created.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task list created.'); return res.data?.data ?? null; }), [request] @@ -158,12 +143,7 @@ export function AdminTaskProvider({ children }) { (taskListId, payload) => request(async () => { const res = await api.patch(`${BASE}/${taskListId}`, payload); - toast('Task list updated.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task list updated.'); return res.data?.data ?? null; }), [request] @@ -173,12 +153,7 @@ export function AdminTaskProvider({ children }) { (taskListId) => request(async () => { await api.delete(`${BASE}/${taskListId}`); - toast('Task list archived.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task list archived.'); return true; }), [request] @@ -188,12 +163,7 @@ export function AdminTaskProvider({ children }) { (taskListId) => request(async () => { await api.patch(`${BASE}/${taskListId}/restore`); - toast('Task list restored.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task list restored.'); return true; }), [request] @@ -203,12 +173,7 @@ export function AdminTaskProvider({ children }) { (ids) => request(async () => { await api.post(`${BASE}/bulk-archive`, { ids }); - toast(`${ids.length} task list(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} task list(s) archived.`); return true; }), [request] @@ -218,12 +183,7 @@ export function AdminTaskProvider({ children }) { (ids) => request(async () => { await api.post(`${BASE}/bulk-restore`, { ids }); - toast(`${ids.length} task list(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} task list(s) restored.`); return true; }), [request] @@ -233,12 +193,7 @@ export function AdminTaskProvider({ children }) { (taskListId) => request(async () => { await api.delete(`${BASE}/${taskListId}/permanent`); - toast('Task list permanently deleted.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task list permanently deleted.'); return true; }), [request] @@ -248,12 +203,7 @@ export function AdminTaskProvider({ children }) { (ids) => request(async () => { await api.post(`${BASE}/bulk-delete`, { ids }); - toast(`${ids.length} task list(s) permanently deleted.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} task list(s) permanently deleted.`); return true; }), [request] @@ -304,19 +254,9 @@ export function AdminTaskProvider({ children }) { }); const result = res.data?.data ?? {}; if (result.assigned_ids?.length) { - toast(`${result.assigned_ids.length} group(s) assigned.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${result.assigned_ids.length} group(s) assigned.`); } else { - toast('All selected groups were already assigned.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('All selected groups were already assigned.'); } return result; }), @@ -330,12 +270,7 @@ export function AdminTaskProvider({ children }) { group_ids: groupIds, }); const result = res.data?.data ?? {}; - toast(`${result.unassigned_ids?.length ?? 0} group(s) unassigned.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${result.unassigned_ids?.length ?? 0} group(s) unassigned.`); return result; }), [request] @@ -396,12 +331,7 @@ export function AdminTaskProvider({ children }) { (taskListId, payload) => request(async () => { const res = await api.post(`${BASE}/${taskListId}/tasks`, payload); - toast('Task created.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task created.'); return res.data?.data?.data ?? null; }), [request] @@ -411,12 +341,7 @@ export function AdminTaskProvider({ children }) { (taskListId, taskId, payload) => request(async () => { const res = await api.patch(`${BASE}/${taskListId}/tasks/${taskId}`, payload); - toast('Task updated.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task updated.'); return res.data?.data?.data ?? null; }), [request] @@ -426,12 +351,7 @@ export function AdminTaskProvider({ children }) { (taskListId, taskId) => request(async () => { await api.delete(`${BASE}/${taskListId}/tasks/${taskId}`); - toast('Task archived.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task archived.'); return true; }), [request] @@ -441,12 +361,7 @@ export function AdminTaskProvider({ children }) { (taskListId, taskId) => request(async () => { await api.patch(`${BASE}/${taskListId}/tasks/${taskId}/restore`); - toast('Task restored.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task restored.'); return true; }), [request] @@ -456,12 +371,7 @@ export function AdminTaskProvider({ children }) { (taskListId, ids) => request(async () => { await api.post(`${BASE}/${taskListId}/tasks/bulk-archive`, { ids }); - toast(`${ids.length} task(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} task(s) archived.`); return true; }), [request] @@ -471,12 +381,7 @@ export function AdminTaskProvider({ children }) { (taskListId, ids) => request(async () => { await api.post(`${BASE}/${taskListId}/tasks/bulk-restore`, { ids }); - toast(`${ids.length} task(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} task(s) restored.`); return true; }), [request] @@ -486,12 +391,7 @@ export function AdminTaskProvider({ children }) { (taskListId, taskId) => request(async () => { await api.delete(`${BASE}/${taskListId}/tasks/${taskId}/permanent`); - toast('Task permanently deleted.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task permanently deleted.'); return true; }), [request] @@ -501,12 +401,7 @@ export function AdminTaskProvider({ children }) { (taskListId, ids) => request(async () => { await api.post(`${BASE}/${taskListId}/tasks/bulk-delete`, { ids }); - toast(`${ids.length} task(s) permanently deleted.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} task(s) permanently deleted.`); return true; }), [request] @@ -625,12 +520,7 @@ export function AdminTaskProvider({ children }) { await api.delete( `${BASE}/${taskListId}/tasks/${taskId}/completions/${completionId}` ); - toast('Completion archived.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Completion archived.'); return true; }), [completionRequest] @@ -643,12 +533,7 @@ export function AdminTaskProvider({ children }) { await api.patch( `${BASE}/${taskListId}/tasks/${taskId}/completions/${completionId}/restore` ); - toast('Completion restored.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Completion restored.'); return true; }), [completionRequest] @@ -662,12 +547,7 @@ export function AdminTaskProvider({ children }) { `${BASE}/${taskListId}/tasks/${taskId}/completions/bulk-archive`, { ids } ); - toast(`${ids.length} completion(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} completion(s) archived.`); return true; }), [completionRequest] @@ -681,12 +561,7 @@ export function AdminTaskProvider({ children }) { `${BASE}/${taskListId}/tasks/${taskId}/completions/bulk-restore`, { ids } ); - toast(`${ids.length} completion(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${ids.length} completion(s) restored.`); return true; }), [completionRequest] diff --git a/src/contexts/AdminTierCategoriesContext.jsx b/src/contexts/AdminTierCategoriesContext.jsx index cd72b4a..834b305 100644 --- a/src/contexts/AdminTierCategoriesContext.jsx +++ b/src/contexts/AdminTierCategoriesContext.jsx @@ -19,12 +19,7 @@ export function AdminTierCategoriesProvider({ children }) { setLoading(true); try { return await fn(); } catch (err) { - toast(err?.response?.data?.message ?? "Something went wrong.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Something went wrong."); return null; } finally { setLoading(false); } }, []); @@ -46,12 +41,7 @@ export function AdminTierCategoriesProvider({ children }) { const createCategory = useCallback((payload) => request(async () => { const { data } = await api.post("/admin/tiers/categories", payload); - toast("Tier category created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Tier category created."); return data.data; }), [request]); @@ -62,12 +52,7 @@ export function AdminTierCategoriesProvider({ children }) { prev.map((c) => (String(c.tier_category_id) === String(id) ? data.data : c)) ); if (category && String(category.tier_category_id) === String(id)) setCategory(data.data); - toast("Tier category updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Tier category updated."); return data.data; }), [request, category]); @@ -75,12 +60,7 @@ export function AdminTierCategoriesProvider({ children }) { request(async () => { await api.delete(`/admin/tiers/categories/${id}`); setCategories((prev) => prev.filter((c) => String(c.tier_category_id) !== String(id))); - toast("Tier category deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Tier category deleted."); return true; }), [request]); diff --git a/src/contexts/AdminTierPoliciesContext.jsx b/src/contexts/AdminTierPoliciesContext.jsx index f338564..606a04f 100644 --- a/src/contexts/AdminTierPoliciesContext.jsx +++ b/src/contexts/AdminTierPoliciesContext.jsx @@ -20,12 +20,7 @@ export function AdminTierPoliciesProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -50,12 +45,7 @@ export function AdminTierPoliciesProvider({ children }) { ? prev.map((b) => (b.key === key ? data.data : b)) : [...prev, data.data]; }); - toast("Badge saved.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Badge saved."); return data.data; }), [request]); diff --git a/src/contexts/AdminTiersContext.jsx b/src/contexts/AdminTiersContext.jsx index 6a33ca4..eda4fa7 100644 --- a/src/contexts/AdminTiersContext.jsx +++ b/src/contexts/AdminTiersContext.jsx @@ -33,12 +33,7 @@ export function AdminTiersProvider({ children }) { totalPages: data.data?.pagination?.totalPages ?? 1, totalRecords: data.data?.pagination?.totalRecords ?? 0, }); - } catch { toast("Could not load plans.", { - action: { - label: "Close", - onClick: () => {} - } - }); } + } catch { toast("Could not load plans."); } finally { setLoading(false); } }, []); @@ -47,12 +42,7 @@ export function AdminTiersProvider({ children }) { try { const { data } = await api.get(`/admin/tiers/${id}`); setPlan(data.data ?? null); - } catch { toast("Could not load plan.", { - action: { - label: "Close", - onClick: () => {} - } - }); } + } catch { toast("Could not load plan."); } finally { setLoading(false); } }, []); @@ -60,20 +50,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { const { data } = await api.post("/admin/tiers", payload); - toast("Plan created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plan created."); return data.data; } catch (err) { - toast(err?.response?.data?.message ?? "Could not create plan.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not create plan."); return null; } finally { setLoading(false); } }, []); @@ -82,20 +62,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { const { data } = await api.put(`/admin/tiers/${id}`, payload); - toast("Plan updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plan updated."); return data.data; } catch (err) { - toast(err?.response?.data?.message ?? "Could not update plan.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not update plan."); return null; } finally { setLoading(false); } }, []); @@ -104,20 +74,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.delete(`/admin/tiers/${id}`); - toast("Plan archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plan archived."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not archive plan.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not archive plan."); return false; } finally { setLoading(false); } }, []); @@ -126,20 +86,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.post(`/admin/tiers/${id}/restore`); - toast("Plan restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plan restored."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not restore plan.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not restore plan."); return false; } finally { setLoading(false); } }, []); @@ -148,20 +98,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.post("/admin/tiers/bulk/archive", { ids }); - toast("Plans archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plans archived."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not archive plans.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not archive plans."); return false; } finally { setLoading(false); } }, []); @@ -170,20 +110,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.post("/admin/tiers/bulk/restore", { ids }); - toast("Plans restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plans restored."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not restore plans.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not restore plans."); return false; } finally { setLoading(false); } }, []); @@ -192,20 +122,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.delete(`/admin/tiers/${id}/permanent`); - toast("Plan permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plan permanently deleted."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not permanently delete plan.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not permanently delete plan."); return false; } finally { setLoading(false); } }, []); @@ -214,20 +134,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.post("/admin/tiers/bulk/permanent-delete", { ids }); - toast("Plans permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Plans permanently deleted."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not permanently delete plans.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not permanently delete plans."); return false; } finally { setLoading(false); } }, []); @@ -252,12 +162,7 @@ export function AdminTiersProvider({ children }) { try { const { data } = await api.get(`/admin/tiers/users/${userId}/tiers`); setUserTiers(data.data ?? []); - } catch { toast("Could not load user tiers.", { - action: { - label: "Close", - onClick: () => {} - } - }); } + } catch { toast("Could not load user tiers."); } finally { setLoading(false); } }, []); @@ -265,20 +170,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.post("/admin/tiers/users/tiers/grant", payload); - toast("Tier granted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Tier granted."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not grant tier.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not grant tier."); return false; } finally { setLoading(false); } }, []); @@ -287,20 +182,10 @@ export function AdminTiersProvider({ children }) { setLoading(true); try { await api.patch(`/admin/tiers/users/tiers/${tid}/revoke`); - toast("Tier revoked.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Tier revoked."); return true; } catch (err) { - toast(err?.response?.data?.message ?? "Could not revoke tier.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not revoke tier."); return false; } finally { setLoading(false); } }, []); @@ -321,12 +206,7 @@ export function AdminTiersProvider({ children }) { totalPages: data.data?.pagination?.totalPages ?? 1, totalRecords: data.data?.pagination?.totalRecords ?? 0, }); - } catch { toast("Could not load payments.", { - action: { - label: "Close", - onClick: () => {} - } - }); } + } catch { toast("Could not load payments."); } finally { setLoading(false); } }, []); @@ -335,12 +215,7 @@ export function AdminTiersProvider({ children }) { try { const { data } = await api.get(`/admin/tiers/payments/${id}`); setPayment(data.data ?? null); - } catch { toast("Could not load payment.", { - action: { - label: "Close", - onClick: () => {} - } - }); } + } catch { toast("Could not load payment."); } finally { setLoading(false); } }, []); diff --git a/src/contexts/AdminUserContext.jsx b/src/contexts/AdminUserContext.jsx index 303bf3b..b441e3d 100644 --- a/src/contexts/AdminUserContext.jsx +++ b/src/contexts/AdminUserContext.jsx @@ -45,12 +45,7 @@ export const UserProvider = ({ children }) => { } catch (err) { const message = err?.response?.data?.message || err.message || "Something went wrong."; setError(message); - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -113,12 +108,7 @@ export const UserProvider = ({ children }) => { (payload) => request(async () => { const res = await api.post(`${BASE}/users/staff`, payload); - toast("Staff user added successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Staff user added successfully."); return res.data; }), [request] @@ -132,12 +122,7 @@ export const UserProvider = ({ children }) => { setUsers((prev) => prev.map((u) => (u.user_id === userId ? { ...u, ...res.data?.data } : u)) ); - toast("User updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("User updated successfully."); return res.data; }), [request] @@ -151,12 +136,7 @@ export const UserProvider = ({ children }) => { setUsers((prev) => prev.map((u) => (u.user_id === userId ? { ...u, is_active: false } : u)) ); - toast("User deactivated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("User deactivated."); return res.data; }), [request] @@ -174,12 +154,7 @@ export const UserProvider = ({ children }) => { deactivated_ids.includes(u.user_id) ? { ...u, is_active: false } : u ) ); - toast(`${deactivated_ids.length} user(s) deactivated.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${deactivated_ids.length} user(s) deactivated.`); } return res.data; }), @@ -194,12 +169,7 @@ export const UserProvider = ({ children }) => { setUsers((prev) => prev.map((u) => (u.user_id === userId ? { ...u, is_active: true } : u)) ); - toast("User restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("User restored."); return res.data; }), [request] @@ -217,12 +187,7 @@ export const UserProvider = ({ children }) => { restored_ids.includes(u.user_id) ? { ...u, is_active: true } : u ) ); - toast(`${restored_ids.length} user(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${restored_ids.length} user(s) restored.`); } return res.data; }), @@ -235,12 +200,7 @@ export const UserProvider = ({ children }) => { request(async () => { const res = await api.delete(`${BASE}/users/${userId}/permanent`); setUsers((prev) => prev.filter((u) => u.user_id !== userId)); - toast("User permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("User permanently deleted."); return res.data; }), [request] @@ -254,12 +214,7 @@ export const UserProvider = ({ children }) => { const { deleted_ids } = res.data?.data ?? {}; if (deleted_ids?.length) { setUsers((prev) => prev.filter((u) => !deleted_ids.includes(u.user_id))); - toast(`${deleted_ids.length} user(s) permanently deleted.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${deleted_ids.length} user(s) permanently deleted.`); } return res.data; }), @@ -285,12 +240,7 @@ export const UserProvider = ({ children }) => { setSessions((prev) => prev.map((s) => (s.session_id === sessionId ? { ...s, is_active: false } : s)) ); - toast("Session terminated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Session terminated."); return res.data; }), [request] @@ -334,12 +284,7 @@ export const UserProvider = ({ children }) => { }); return d; } catch (err) { - toast(err?.response?.data?.message ?? "Could not load activity.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load activity."); return null; } finally { setActivityLoading(false); @@ -366,12 +311,7 @@ export const UserProvider = ({ children }) => { setAchievements(res.data?.data ?? []); return res.data?.data; } catch (err) { - toast(err?.response?.data?.message ?? "Could not load achievements.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load achievements."); return []; } finally { setAchievementsLoading(false); @@ -387,12 +327,7 @@ export const UserProvider = ({ children }) => { prev.map((u) => (u.user_id === userId ? { ...u, is_banned: true } : u)) ); if (user?.user_id === userId) setUser((prev) => prev ? { ...prev, is_banned: true } : prev); - toast("User banned successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("User banned successfully."); return res.data; }), [request, user] @@ -407,12 +342,7 @@ export const UserProvider = ({ children }) => { prev.map((u) => (u.user_id === userId ? { ...u, is_banned: false } : u)) ); if (user?.user_id === userId) setUser((prev) => prev ? { ...prev, is_banned: false } : prev); - toast("User unbanned successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("User unbanned successfully."); return res.data; }), [request, user] @@ -428,12 +358,7 @@ export const UserProvider = ({ children }) => { setUsers((prev) => prev.map((u) => (banned_ids.includes(u.user_id) ? { ...u, is_banned: true } : u)) ); - toast(`${banned_ids.length} user(s) banned.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${banned_ids.length} user(s) banned.`); } return res.data; }), @@ -450,12 +375,7 @@ export const UserProvider = ({ children }) => { setUsers((prev) => prev.map((u) => (unbanned_ids.includes(u.user_id) ? { ...u, is_banned: false } : u)) ); - toast(`${unbanned_ids.length} user(s) unbanned.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${unbanned_ids.length} user(s) unbanned.`); } return res.data; }), @@ -470,12 +390,7 @@ export const UserProvider = ({ children }) => { setBans(res.data?.data ?? []); return res.data?.data; } catch (err) { - toast(err?.response?.data?.message ?? "Could not load ban history.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load ban history."); return []; } finally { setBansLoading(false); diff --git a/src/contexts/AdminUserGroupContext.jsx b/src/contexts/AdminUserGroupContext.jsx index 7267595..d5ac3e1 100644 --- a/src/contexts/AdminUserGroupContext.jsx +++ b/src/contexts/AdminUserGroupContext.jsx @@ -27,12 +27,7 @@ export function UserGroupProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? "Something went wrong."; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -126,12 +121,7 @@ export function UserGroupProvider({ children }) { request(async () => { const res = await api.post(`${BASE}/groups`, { name, description, group_code }); setGroups((prev) => [res.data?.data, ...prev]); - toast("Group created successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Group created successfully."); return res.data; }), [request] @@ -146,12 +136,7 @@ export function UserGroupProvider({ children }) { prev.map((g) => (g.group_id === gid ? { ...g, ...res.data?.data } : g)) ); setGroup((prev) => (prev?.group_id === gid ? { ...prev, ...res.data?.data } : prev)); - toast("Group updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Group updated successfully."); return res.data; }), [request] @@ -165,12 +150,7 @@ export function UserGroupProvider({ children }) { setGroups((prev) => prev.map((g) => (g.group_id === gid ? { ...g, is_active: false } : g)) ); - toast("Group deactivated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Group deactivated."); return res.data; }), [request] @@ -188,12 +168,7 @@ export function UserGroupProvider({ children }) { deactivated_ids.includes(g.group_id) ? { ...g, is_active: false } : g ) ); - toast(`${deactivated_ids.length} group(s) deactivated.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${deactivated_ids.length} group(s) deactivated.`); } return res.data; }), @@ -208,12 +183,7 @@ export function UserGroupProvider({ children }) { setGroups((prev) => prev.map((g) => (g.group_id === gid ? { ...g, is_active: true } : g)) ); - toast("Group restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Group restored."); return res.data; }), [request] @@ -231,12 +201,7 @@ export function UserGroupProvider({ children }) { restored_ids.includes(g.group_id) ? { ...g, is_active: true } : g ) ); - toast(`${restored_ids.length} group(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${restored_ids.length} group(s) restored.`); } return res.data; }), @@ -249,12 +214,7 @@ export function UserGroupProvider({ children }) { request(async () => { const res = await api.delete(`${BASE}/groups/${gid}/permanent`); setGroups((prev) => prev.filter((g) => g.group_id !== gid)); - toast("Group permanently deleted.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Group permanently deleted."); return res.data; }), [request] @@ -268,12 +228,7 @@ export function UserGroupProvider({ children }) { const { deleted_ids } = res.data?.data ?? {}; if (deleted_ids?.length) { setGroups((prev) => prev.filter((g) => !deleted_ids.includes(g.group_id))); - toast(`${deleted_ids.length} group(s) permanently deleted.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${deleted_ids.length} group(s) permanently deleted.`); } return res.data; }), @@ -286,12 +241,7 @@ export function UserGroupProvider({ children }) { request(async () => { const res = await api.post(`${BASE}/groups/${gid}/users`, { user_ids }); setUsersNotIn((prev) => prev.filter((u) => !user_ids.includes(u.user_id))); - toast("Users added to group.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Users added to group."); return res.data; }), [request] @@ -304,12 +254,7 @@ export function UserGroupProvider({ children }) { const res = await api.delete(`${BASE}/groups/${gid}/users`, { data: { user_ids } }); setMembers((prev) => prev.filter((u) => !user_ids.includes(u.user_id))); setUsersIn((prev) => prev.filter((u) => !user_ids.includes(u.user_id))); - toast("Users removed from group.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Users removed from group."); return res.data; }), [request] diff --git a/src/contexts/AuthContext.jsx b/src/contexts/AuthContext.jsx index 28db510..0f2a650 100644 --- a/src/contexts/AuthContext.jsx +++ b/src/contexts/AuthContext.jsx @@ -97,6 +97,18 @@ export function AuthProvider({ children }) { return { success: true, email: data.data.email } } catch (err) { const message = err.response?.data?.message || 'Could not process request.' + const isGoogleAccount = err.response?.data?.errors?.google === true + return { success: false, message, isGoogleAccount } + } + }, []) + + // ── Verify reset OTP (step 2 — does not change the password) ────────────── + const verifyResetOtp = useCallback(async ({ email, otp }) => { + try { + await api.post('/auth/verify-reset-otp', { email, otp }) + return { success: true } + } catch (err) { + const message = err.response?.data?.message || 'Verification failed.' return { success: false, message } } }, []) @@ -158,6 +170,7 @@ export function AuthProvider({ children }) { verifyOTP, resendOTP, forgotPassword, + verifyResetOtp, resetPassword, logout, loading, diff --git a/src/contexts/ClientCourseReadingProgressContext.jsx b/src/contexts/ClientCourseReadingProgressContext.jsx index 0f2da98..eba442d 100644 --- a/src/contexts/ClientCourseReadingProgressContext.jsx +++ b/src/contexts/ClientCourseReadingProgressContext.jsx @@ -58,12 +58,7 @@ export function CourseReadingProgressProvider({ children }) { ); return rows; } catch (err) { - toast(err?.response?.data?.message ?? 'Could not load course progress.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? 'Could not load course progress.'); return null; } finally { setLoading(false); @@ -106,12 +101,7 @@ export function CourseReadingProgressProvider({ children }) { delete next[lessonUuid]; return next; }); - toast(err?.response?.data?.message ?? 'Could not update progress.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? 'Could not update progress.'); return null; } }, []); diff --git a/src/contexts/ClientCoursesContext.jsx b/src/contexts/ClientCoursesContext.jsx index d10cf25..fa99fbd 100644 --- a/src/contexts/ClientCoursesContext.jsx +++ b/src/contexts/ClientCoursesContext.jsx @@ -42,12 +42,7 @@ export function ClientCoursesProvider({ children }) { const { data } = await api.get("/client/courses"); setCourses(data.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load courses.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load courses."); } finally { setCoursesLoading(false); } @@ -63,12 +58,7 @@ export function ClientCoursesProvider({ children }) { if (err?.response?.status === 403) { setCourseBlocked(true); // let the UI show an upgrade prompt } else { - toast(err?.response?.data?.message ?? "Could not load course.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load course."); } } finally { setCourseLoading(false); @@ -81,12 +71,7 @@ export function ClientCoursesProvider({ children }) { const { data } = await api.get(`/client/courses/${courseId}/units/${unitId}`); setUnit(data.data ?? null); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load unit.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load unit."); } finally { setUnitLoading(false); } @@ -100,12 +85,7 @@ export function ClientCoursesProvider({ children }) { ); setLesson(data.data ?? null); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load lesson.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load lesson."); } finally { setLessonLoading(false); } @@ -119,12 +99,7 @@ export function ClientCoursesProvider({ children }) { ); setQuiz(data.data ?? null); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load quiz.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load quiz."); } finally { setQuizLoading(false); } @@ -136,12 +111,7 @@ export function ClientCoursesProvider({ children }) { const { data } = await api.get(`/client/courses/${courseId}/assessment`); setAssessment(data.data ?? null); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load assessment.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load assessment."); } finally { setAssessmentLoading(false); } @@ -155,12 +125,7 @@ export function ClientCoursesProvider({ children }) { ); return data.data ?? null; } catch (err) { - toast(err?.response?.data?.message ?? "Could not submit quiz.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not submit quiz."); return null; } }, []); @@ -170,12 +135,7 @@ export function ClientCoursesProvider({ children }) { const { data } = await api.post(`/client/courses/${courseId}/assessment/${assessmentId}/start`); return data.data ?? null; } catch (err) { - toast(err?.response?.data?.message ?? "Could not start assessment.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not start assessment."); return null; } }, []); @@ -207,12 +167,7 @@ export function ClientCoursesProvider({ children }) { ); return data.data ?? null; } catch (err) { - toast(err?.response?.data?.message ?? "Could not submit assessment.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not submit assessment."); return null; } }, []); @@ -229,12 +184,7 @@ export function ClientCoursesProvider({ children }) { const { data } = await api.get("/client/course-purchases"); setPurchases(data.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load purchases.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load purchases."); } finally { setPurchasesLoading(false); } }, []); @@ -244,12 +194,7 @@ export function ClientCoursesProvider({ children }) { const { data } = await api.post("/client/course-purchases/order", { product_id: productId }); return data.data ?? null; } catch (err) { - toast(err?.response?.data?.message ?? "Could not create order.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not create order."); return null; } finally { setPurchaseLoading(false); } }, []); @@ -258,20 +203,10 @@ export function ClientCoursesProvider({ children }) { setPurchaseLoading(true); try { const { data } = await api.post("/client/course-purchases/capture", { order_id: orderId }); - toast("Purchase confirmed! You now have access to this course.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Purchase confirmed! You now have access to this course."); return data.data ?? null; } catch (err) { - toast(err?.response?.data?.message ?? "Could not capture payment.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not capture payment."); return null; } finally { setPurchaseLoading(false); } }, []); diff --git a/src/contexts/ClientGroupContext.jsx b/src/contexts/ClientGroupContext.jsx index b9a5293..0ae3724 100644 --- a/src/contexts/ClientGroupContext.jsx +++ b/src/contexts/ClientGroupContext.jsx @@ -30,12 +30,7 @@ export function GroupProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? 'Something went wrong.'; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); diff --git a/src/contexts/ClientTaskContext.jsx b/src/contexts/ClientTaskContext.jsx index 5d99145..7940eb6 100644 --- a/src/contexts/ClientTaskContext.jsx +++ b/src/contexts/ClientTaskContext.jsx @@ -45,12 +45,7 @@ export function TaskProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? 'Something went wrong.'; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -63,12 +58,7 @@ export function TaskProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? 'Something went wrong.'; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setCompletionLoading(false); @@ -165,12 +155,7 @@ export function TaskProvider({ children }) { payload ); const data = res.data?.data ?? null; - toast('Task submitted successfully.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Task submitted successfully.'); // Immediately update latest completion so UI reflects the new state setLatestCompletion(data); // Prepend to history if it's already loaded diff --git a/src/contexts/ClientTaskProgressContext.jsx b/src/contexts/ClientTaskProgressContext.jsx index a75403c..3fd06f6 100644 --- a/src/contexts/ClientTaskProgressContext.jsx +++ b/src/contexts/ClientTaskProgressContext.jsx @@ -45,12 +45,7 @@ export function TaskProgressProvider({ children }) { return await fn(); } catch (err) { const message = err?.response?.data?.message ?? 'Something went wrong.'; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); diff --git a/src/contexts/ClientTiersProvider.jsx b/src/contexts/ClientTiersProvider.jsx index dac2593..eaf5e06 100644 --- a/src/contexts/ClientTiersProvider.jsx +++ b/src/contexts/ClientTiersProvider.jsx @@ -44,20 +44,10 @@ export function ClientTiersProvider({ children }) { // known-active tier — this prevents the "Free" flash after an upgrade. setMyTier(prev => (tier === null && prev?.status === 'active') ? prev : tier); if (tier?.just_expired) { - toast("Your subscription has expired. You've been moved to the Free plan.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Your subscription has expired. You've been moved to the Free plan."); } } catch (err) { - if (!silent) toast(err?.response?.data?.message ?? "Could not load tier.", { - action: { - label: "Close", - onClick: () => {} - } - }); + if (!silent) toast(err?.response?.data?.message ?? "Could not load tier."); } finally { if (!silent) setTierLoading(false); } @@ -88,12 +78,7 @@ export function ClientTiersProvider({ children }) { const { data } = await api.get("/client/tiers/me/history"); setTierHistory(data.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load tier history.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load tier history."); } finally { setTierHistoryLoading(false); } @@ -105,12 +90,7 @@ export function ClientTiersProvider({ children }) { const { data } = await api.get("/client/tiers/plans"); setPlans(data.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load plans.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load plans."); } finally { setPlansLoading(false); } @@ -138,12 +118,7 @@ export function ClientTiersProvider({ children }) { const { data } = await api.post("/client/tiers/checkout/order", payload); return data.data ?? null; } catch (err) { - toast(err?.response?.data?.message ?? "Could not create order.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not create order."); return null; } finally { setCheckoutLoading(false); @@ -155,24 +130,14 @@ export function ClientTiersProvider({ children }) { setCheckoutLoading(true); try { const { data } = await api.post("/client/tiers/checkout/capture", { order_id }); - toast(data.message ?? "Payment successful. Tier activated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(data.message ?? "Payment successful. Tier activated."); setMyTier({ tier: data.data?.tier, expires_at: data.data?.expires_at, status: "active" }); // Refresh from server so the browser cache holds fresh Premium data — prevents // subsequent getMyTier() calls from getting a stale 304 with the old Free/null response. getMyTier({ silent: true }); return data.data ?? null; } catch (err) { - toast(err?.response?.data?.message ?? "Payment capture failed.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Payment capture failed."); return null; } finally { setCheckoutLoading(false); @@ -197,12 +162,7 @@ export function ClientTiersProvider({ children }) { const { data } = await api.get("/client/tiers/me/payments"); setPayments(data.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load payment history.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load payment history."); } finally { setPaymentsLoading(false); } diff --git a/src/contexts/ProfileProvider.jsx b/src/contexts/ProfileProvider.jsx index 0e47bc2..3f7540d 100644 --- a/src/contexts/ProfileProvider.jsx +++ b/src/contexts/ProfileProvider.jsx @@ -34,12 +34,7 @@ export function ProfileProvider({ children, apiBase = '/client' }) { setProfile(fresh); return fresh; } catch (err) { - toast(err?.response?.data?.message ?? "Could not load profile.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load profile."); return null; } finally { setProfileLoading(false); @@ -52,20 +47,10 @@ export function ProfileProvider({ children, apiBase = '/client' }) { const { data } = await api.put(`${apiBase}/profile`, { personal_info }); setProfile(data.data ?? null); setUser((prev) => ({ ...prev, ...data.data })); - toast(data.message ?? "Profile updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(data.message ?? "Profile updated."); return { success: true, data: data.data }; } catch (err) { - toast(err?.response?.data?.message ?? "Could not update profile.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not update profile."); return { success: false }; } finally { setProfileLoading(false); @@ -78,12 +63,7 @@ export function ProfileProvider({ children, apiBase = '/client' }) { const { data } = await api.get(`${apiBase}/sessions`); setSessions(data.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load sessions.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load sessions."); } finally { setSessionsLoading(false); } @@ -94,20 +74,10 @@ export function ProfileProvider({ children, apiBase = '/client' }) { try { await api.delete(`${apiBase}/sessions/${sessionId}`); setSessions((prev) => prev.filter((s) => s.session_id !== sessionId)); - toast("Session revoked.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Session revoked."); return { success: true }; } catch (err) { - toast(err?.response?.data?.message ?? "Could not revoke session.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not revoke session."); return { success: false }; } finally { setRevokingId(null); @@ -122,20 +92,10 @@ export function ProfileProvider({ children, apiBase = '/client' }) { const { data } = await api.post(`${apiBase}/profile/avatar`, formData); setProfile(data.data ?? null); setUser((prev) => ({ ...prev, personal_info: data.data?.personal_info })); - toast('Avatar updated.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Avatar updated.'); return { success: true }; } catch (err) { - toast(err?.response?.data?.message ?? 'Could not update avatar.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? 'Could not update avatar.'); return { success: false }; } finally { setAvatarLoading(false); @@ -154,20 +114,10 @@ export function ProfileProvider({ children, apiBase = '/client' }) { ...prev, personal_info: { ...(prev?.personal_info ?? {}), avatar: null }, })); - toast('Avatar removed.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Avatar removed.'); return { success: true }; } catch (err) { - toast(err?.response?.data?.message ?? 'Could not remove avatar.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? 'Could not remove avatar.'); return { success: false }; } finally { setAvatarLoading(false); @@ -180,12 +130,7 @@ export function ProfileProvider({ children, apiBase = '/client' }) { const { data } = await api.get(`${apiBase}/achievements`); setAchievements(data.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Could not load achievements.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load achievements."); } finally { setAchievementsLoading(false); } diff --git a/src/contexts/StaffGroupContext.jsx b/src/contexts/StaffGroupContext.jsx index ca4b487..56536da 100644 --- a/src/contexts/StaffGroupContext.jsx +++ b/src/contexts/StaffGroupContext.jsx @@ -41,12 +41,7 @@ export const StaffGroupProvider = ({ children }) => { } catch (err) { const message = err?.response?.data?.message || err.message || "Something went wrong."; setError(message); - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -61,12 +56,7 @@ export const StaffGroupProvider = ({ children }) => { return await fn(); } catch (err) { const message = err?.response?.data?.message || err.message || "Something went wrong."; - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setMembersLoading(false); diff --git a/src/contexts/StaffScoreContext.jsx b/src/contexts/StaffScoreContext.jsx index 77b99dc..1744160 100644 --- a/src/contexts/StaffScoreContext.jsx +++ b/src/contexts/StaffScoreContext.jsx @@ -27,12 +27,7 @@ export const StaffScoreProvider = ({ children }) => { } catch (err) { const message = err?.response?.data?.message || err.message || "Something went wrong."; setError(message); - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); diff --git a/src/contexts/StaffTaskContext.jsx b/src/contexts/StaffTaskContext.jsx index d389ce9..aaebb4b 100644 --- a/src/contexts/StaffTaskContext.jsx +++ b/src/contexts/StaffTaskContext.jsx @@ -53,12 +53,7 @@ export const StaffTaskProvider = ({ children }) => { } catch (err) { const message = err?.response?.data?.message || err.message || "Something went wrong."; setError(message); - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); @@ -139,12 +134,7 @@ export const StaffTaskProvider = ({ children }) => { const res = await api.post(`${BASE}/task-lists`, payload); const created = res.data?.data; if (created) setTaskLists((prev) => [created, ...prev]); - toast("Task list created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task list created."); return res.data; }), [request] @@ -163,12 +153,7 @@ export const StaffTaskProvider = ({ children }) => { if (taskList?.task_list_id === taskListId) setTaskList((prev) => ({ ...prev, ...updated })); } - toast("Task list updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task list updated."); return res.data; }), [request, taskList] @@ -181,12 +166,7 @@ export const StaffTaskProvider = ({ children }) => { const res = await api.post(`${BASE}/task-lists/${taskListId}/archive`); setTaskLists((prev) => prev.filter((tl) => tl.task_list_id !== taskListId)); if (taskList?.task_list_id === taskListId) setTaskList(null); - toast("Task list archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task list archived."); return res.data; }), [request, taskList] @@ -198,12 +178,7 @@ export const StaffTaskProvider = ({ children }) => { request(async () => { const res = await api.post(`${BASE}/task-lists/${taskListId}/restore`); setTaskLists((prev) => prev.filter((tl) => tl.task_list_id !== taskListId)); - toast("Task list restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task list restored."); return res.data; }), [request] @@ -218,12 +193,7 @@ export const StaffTaskProvider = ({ children }) => { setTaskLists((prev) => prev.filter((tl) => !archived_ids.includes(tl.task_list_id)) ); - toast(`${archived_ids.length} task list(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${archived_ids.length} task list(s) archived.`); return res.data; }), [request] @@ -238,12 +208,7 @@ export const StaffTaskProvider = ({ children }) => { setTaskLists((prev) => prev.filter((tl) => !restored_ids.includes(tl.task_list_id)) ); - toast(`${restored_ids.length} task list(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${restored_ids.length} task list(s) restored.`); return res.data; }), [request] @@ -325,12 +290,7 @@ export const StaffTaskProvider = ({ children }) => { ) ); } - toast("Task created.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task created."); return res.data; }), [request] @@ -360,12 +320,7 @@ export const StaffTaskProvider = ({ children }) => { ); if (task?.task_id === taskId) setTask((prev) => ({ ...prev, ...updated })); } - toast("Task updated.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task updated."); return res.data; }), [request, task] @@ -384,12 +339,7 @@ export const StaffTaskProvider = ({ children }) => { : tl ) ); - toast("Task archived.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task archived."); return res.data; }), [request] @@ -401,12 +351,7 @@ export const StaffTaskProvider = ({ children }) => { request(async () => { const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks/${taskId}/restore`); setTasks((prev) => prev.filter((t) => t.task_id !== taskId)); - toast("Task restored.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Task restored."); return res.data; }), [request] @@ -419,12 +364,7 @@ export const StaffTaskProvider = ({ children }) => { const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks/bulk-archive`, { ids }); const { archived_ids = [] } = res.data?.data ?? {}; setTasks((prev) => prev.filter((t) => !archived_ids.includes(t.task_id))); - toast(`${archived_ids.length} task(s) archived.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${archived_ids.length} task(s) archived.`); return res.data; }), [request] @@ -437,12 +377,7 @@ export const StaffTaskProvider = ({ children }) => { const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks/bulk-restore`, { ids }); const { restored_ids = [] } = res.data?.data ?? {}; setTasks((prev) => prev.filter((t) => !restored_ids.includes(t.task_id))); - toast(`${restored_ids.length} task(s) restored.`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`${restored_ids.length} task(s) restored.`); return res.data; }), [request] diff --git a/src/contexts/StaffUserContext.jsx b/src/contexts/StaffUserContext.jsx index 1229898..a6cd0ec 100644 --- a/src/contexts/StaffUserContext.jsx +++ b/src/contexts/StaffUserContext.jsx @@ -37,12 +37,7 @@ export const StaffUserProvider = ({ children }) => { } catch (err) { const message = err?.response?.data?.message || err.message || "Something went wrong."; setError(message); - toast(message, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(message); return null; } finally { setLoading(false); diff --git a/src/index.css b/src/index.css index cbf8974..15eb6d7 100644 --- a/src/index.css +++ b/src/index.css @@ -239,6 +239,12 @@ body { @apply bg-background text-foreground; } + + /* Suppress Edge/IE's native password reveal & clear icons so they don't overlap our custom eye toggle */ + input[type="password"]::-ms-reveal, + input[type="password"]::-ms-clear { + display: none; + } } /* Admin route: hardcoded violet accent for dark mode (temporary, reuses existing .dark violet tokens) */ diff --git a/src/modules/admin/pages/courses/CourseAssessment.jsx b/src/modules/admin/pages/courses/CourseAssessment.jsx index 8f2aeac..34ee419 100644 --- a/src/modules/admin/pages/courses/CourseAssessment.jsx +++ b/src/modules/admin/pages/courses/CourseAssessment.jsx @@ -248,12 +248,7 @@ export default function CourseAssessment() { setLocalAssessment(result); } catch (err) { if (err?.response?.status !== 404) { - toast(err?.response?.data?.message ?? "Could not load assessment.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load assessment."); } } finally { setInitializing(false); diff --git a/src/modules/admin/pages/courses/EditCourse.jsx b/src/modules/admin/pages/courses/EditCourse.jsx index 40ff674..3289933 100644 --- a/src/modules/admin/pages/courses/EditCourse.jsx +++ b/src/modules/admin/pages/courses/EditCourse.jsx @@ -290,7 +290,7 @@ export default function EditCourse() { const handleSaveCategories = async () => { setCategoriesLoading(true); - await syncCourseCategories(courseId, selectedCategoryIds.map(Number)); + await syncCourseCategories(courseId, selectedCategoryIds); setCategoriesDirty(false); setCategoriesLoading(false); }; diff --git a/src/modules/admin/pages/courses/ViewAssessment.jsx b/src/modules/admin/pages/courses/ViewAssessment.jsx index 6057206..b249a97 100644 --- a/src/modules/admin/pages/courses/ViewAssessment.jsx +++ b/src/modules/admin/pages/courses/ViewAssessment.jsx @@ -355,12 +355,7 @@ export default function ViewAssessment() { setLocalAssessment(data?.data?.data ?? null); } catch (err) { if (err?.response?.status !== 404) { - toast(err?.response?.data?.message ?? "Could not load assessment.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load assessment."); } } })(); diff --git a/src/modules/admin/pages/courses/units/ModifyQuiz.jsx b/src/modules/admin/pages/courses/units/ModifyQuiz.jsx index 296c45a..a8c5b16 100644 --- a/src/modules/admin/pages/courses/units/ModifyQuiz.jsx +++ b/src/modules/admin/pages/courses/units/ModifyQuiz.jsx @@ -240,12 +240,7 @@ export default function ModifyQuiz() { setLocalQuiz(result); } catch (err) { if (err?.response?.status !== 404) { - toast(err?.response?.data?.message ?? "Could not load quiz.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not load quiz."); } // 404 → no quiz yet, stay in create mode with localQuiz = null } finally { diff --git a/src/modules/admin/pages/notifications/NotificationSettings.jsx b/src/modules/admin/pages/notifications/NotificationSettings.jsx index eb58b2b..0375633 100644 --- a/src/modules/admin/pages/notifications/NotificationSettings.jsx +++ b/src/modules/admin/pages/notifications/NotificationSettings.jsx @@ -41,12 +41,7 @@ export default function NotificationSettings() { const { data } = await api.get("/admin/notification-settings"); setSettings(data?.data ?? []); } catch (err) { - toast(err?.response?.data?.message ?? "Failed to load notification settings.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Failed to load notification settings."); } finally { setLoading(false); } @@ -64,21 +59,10 @@ export default function NotificationSettings() { const updated = data?.data?.data; setSettings((prev) => prev.map((s) => (s.job_name === jobName ? { ...s, ...updated } : s))); toast( - `${JOB_LABELS[jobName]?.label ?? jobName} ${enabled ? "enabled" : "disabled"}.`, - { - action: { - label: "Close", - onClick: () => {} - } - } + `${JOB_LABELS[jobName]?.label ?? jobName} ${enabled ? "enabled" : "disabled"}.` ); } catch (err) { - toast(err?.response?.data?.message ?? "Failed to update setting.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Failed to update setting."); } finally { setSavingJob(null); } @@ -93,19 +77,9 @@ export default function NotificationSettings() { }); const updated = data?.data?.data; setSettings((prev) => prev.map((s) => (s.job_name === jobName ? { ...s, ...updated, preset } : s))); - toast("Schedule updated — took effect immediately, no restart needed.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Schedule updated — took effect immediately, no restart needed."); } catch (err) { - toast(err?.response?.data?.message ?? "Failed to update schedule.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Failed to update schedule."); } finally { setSavingJob(null); } diff --git a/src/modules/admin/pages/tiers/PaymentPolicy.jsx b/src/modules/admin/pages/tiers/PaymentPolicy.jsx index fa1ad0b..d2b7e5f 100644 --- a/src/modules/admin/pages/tiers/PaymentPolicy.jsx +++ b/src/modules/admin/pages/tiers/PaymentPolicy.jsx @@ -100,20 +100,10 @@ export default function PaymentPolicy() { }, promo_rules: promoRules, }); - toast("Payment policy saved.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Payment policy saved."); navigate("/admin/tiers/plans"); } catch (err) { - toast(err?.response?.data?.message ?? "Could not save payment policy.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not save payment policy."); } finally { setSaving(false); } @@ -123,25 +113,10 @@ export default function PaymentPolicy() { const handleAddPromo = () => { const code = addForm.code.trim().toUpperCase(); - if (!code) { toast("Code is required.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; } - if (!addForm.value || Number(addForm.value) <= 0) { toast("Value must be greater than 0.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; } + if (!code) { toast("Code is required."); return; } + if (!addForm.value || Number(addForm.value) <= 0) { toast("Value must be greater than 0."); return; } if (promoRules.some((r) => r.code.toUpperCase() === code)) { - toast("A rule with this code already exists.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; + toast("A rule with this code already exists."); return; } const rule = { diff --git a/src/modules/admin/pages/tiers/ViewPlan.jsx b/src/modules/admin/pages/tiers/ViewPlan.jsx index a2c16fb..ed1ebea 100644 --- a/src/modules/admin/pages/tiers/ViewPlan.jsx +++ b/src/modules/admin/pages/tiers/ViewPlan.jsx @@ -220,19 +220,9 @@ function PaymentPolicyTab({ planId, plan }) { }, promo_rules: promoRules, }); - toast("Payment policy saved.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Payment policy saved."); } catch (err) { - toast(err?.response?.data?.message ?? "Could not save payment policy.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not save payment policy."); } finally { setSaving(false); } @@ -240,24 +230,9 @@ function PaymentPolicyTab({ planId, plan }) { const handleAddPromo = () => { const code = addForm.code.trim().toUpperCase(); - if (!code) { toast("Code is required.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; } - if (!addForm.value || Number(addForm.value) <= 0) { toast("Value must be greater than 0.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; } - if (promoRules.some((r) => r.code.toUpperCase() === code)) { toast("A rule with this code already exists.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; } + if (!code) { toast("Code is required."); return; } + if (!addForm.value || Number(addForm.value) <= 0) { toast("Value must be greater than 0."); return; } + if (promoRules.some((r) => r.code.toUpperCase() === code)) { toast("A rule with this code already exists."); return; } const rule = { code, diff --git a/src/modules/admin/pages/users/EditUser.jsx b/src/modules/admin/pages/users/EditUser.jsx index 02e5283..4272af0 100644 --- a/src/modules/admin/pages/users/EditUser.jsx +++ b/src/modules/admin/pages/users/EditUser.jsx @@ -151,20 +151,10 @@ export default function EditUser() { const res = await updateUser(id, payload); if (res) { - toast("User updated successfully.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("User updated successfully."); navigate(`../view/${id}`); } else { - toast("Failed to update user.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Failed to update user."); } }; diff --git a/src/modules/auth/components/ForgotPasswordForm.jsx b/src/modules/auth/components/ForgotPasswordForm.jsx index f0d8563..d89d437 100644 --- a/src/modules/auth/components/ForgotPasswordForm.jsx +++ b/src/modules/auth/components/ForgotPasswordForm.jsx @@ -5,15 +5,19 @@ * staff, user); only reg_type matters (Google accounts are turned * away, see LoginForm's "Please log in with Google" precedent). * Step 1 — Email (checks reg_type server-side, sends OTP if system) - * Step 2 — OTP + new password + confirm, single submit + * Step 2 — OTP only (verified server-side before advancing) + * Step 3 — New password + confirm, submitted with the verified OTP * Module: User Credentials * Author: lash0000 * Date Created: Jul. 4, 2026 + * Date Modified: Jul. 7, 2026 — split combined OTP+password step into its own + * verify-then-set-password steps (Kenneth Obsequio) ***********************************************************************************************************************************************************************/ import { useState, useEffect } from 'react' -import { useNavigate, Link } from 'react-router-dom' +import { useNavigate, useBlocker, Link } from 'react-router-dom' import { useForm, Controller } from 'react-hook-form' import { useAuth } from '@/contexts/AuthContext' +import { PageMeta } from '@/contexts/MetadataContext' import { z } from 'zod' import { zodResolver } from '@hookform/resolvers/zod' import { cn } from '@/lib/utils' @@ -25,6 +29,7 @@ import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp import { AlertDialog, AlertDialogAction, + AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, @@ -37,9 +42,12 @@ const emailSchema = z.object({ email: z.string().email('Invalid email address'), }) -const resetSchema = z +const otpSchema = z.object({ + otp: z.string().length(6, 'Enter all 6 digits').regex(/^\d{6}$/, 'OTP must contain only digits'), +}) + +const passwordSchema = z .object({ - otp: z.string().length(6, 'Enter all 6 digits').regex(/^\d{6}$/, 'OTP must contain only digits'), new_password: z .string() .min(8, 'Password must be at least 8 characters') @@ -52,19 +60,27 @@ const resetSchema = z path: ['confirm_password'], }) +const STEP_META = [ + { title: 'Forgot Password', description: 'Reset your account password.' }, + { title: 'Enter Verification Code', description: 'Enter the 6-digit code sent to your email.' }, + { title: 'Reset Password', description: 'Choose a new password for your account.' }, +] + export function ForgotPasswordForm({ className, ...props }) { const navigate = useNavigate() - const { forgotPassword, resetPassword } = useAuth() + const { forgotPassword, verifyResetOtp, resetPassword } = useAuth() - // 0 = email, 1 = otp + new password + // 0 = email, 1 = otp, 2 = new password const [step, setStep] = useState(0) const [pendingEmail, setPendingEmail] = useState('') + const [pendingOtp, setPendingOtp] = useState('') const [showPassword, setShowPassword] = useState(false) const [showConfirm, setShowConfirm] = useState(false) const [resendCooldown, setResendCooldown] = useState(0) const [errorDialogOpen, setErrorDialogOpen] = useState(false) const [errorMessage, setErrorMessage] = useState('') const [successDialogOpen, setSuccessDialogOpen] = useState(false) + const [googleDialogOpen, setGoogleDialogOpen] = useState(false) useEffect(() => { if (resendCooldown <= 0) return @@ -72,6 +88,30 @@ export function ForgotPasswordForm({ className, ...props }) { return () => clearTimeout(t) }, [resendCooldown]) + // Past the email step there's an in-progress code/password reset to lose — + // warn on tab close/refresh/URL change (native dialog) and on in-app back/ + // forward navigation (native window.confirm via the router's blocker). + const midFlow = step > 0 + + useEffect(() => { + if (!midFlow) return + const handleBeforeUnload = (e) => { + e.preventDefault() + e.returnValue = '' + } + window.addEventListener('beforeunload', handleBeforeUnload) + return () => window.removeEventListener('beforeunload', handleBeforeUnload) + }, [midFlow]) + + const blocker = useBlocker(midFlow) + + useEffect(() => { + if (blocker.state !== 'blocked') return + const leave = window.confirm('You have an unfinished password reset. Leave this page anyway?') + if (leave) blocker.proceed() + else blocker.reset() + }, [blocker]) + // ── Step 1: Email ────────────────────────────────────────────────────────── const { register: regEmail, @@ -86,8 +126,12 @@ export function ForgotPasswordForm({ className, ...props }) { const result = await forgotPassword({ email }) if (!result.success) { - setErrorMessage(result.message) - setErrorDialogOpen(true) + if (result.isGoogleAccount) { + setGoogleDialogOpen(true) + } else { + setErrorMessage(result.message) + setErrorDialogOpen(true) + } return } @@ -96,19 +140,22 @@ export function ForgotPasswordForm({ className, ...props }) { setStep(1) } - // ── Step 2: OTP + new password ──────────────────────────────────────────── + const handleGoogleSignIn = () => { + window.location.href = '/api/auth/google' + } + + // ── Step 2: OTP ───────────────────────────────────────────────────────────── const { - control: resetControl, - register: regReset, - handleSubmit: submitReset, - formState: { errors: errReset, isSubmitting: isResetting }, + control: otpControl, + handleSubmit: submitOtp, + formState: { errors: errOtp, isSubmitting: isVerifying }, } = useForm({ - resolver: zodResolver(resetSchema), - defaultValues: { otp: '', new_password: '', confirm_password: '' }, + resolver: zodResolver(otpSchema), + defaultValues: { otp: '' }, }) - const onResetSubmit = async ({ otp, new_password }) => { - const result = await resetPassword({ email: pendingEmail, otp, new_password }) + const onOtpSubmit = async ({ otp }) => { + const result = await verifyResetOtp({ email: pendingEmail, otp }) if (!result.success) { setErrorMessage(result.message) @@ -116,7 +163,8 @@ export function ForgotPasswordForm({ className, ...props }) { return } - setSuccessDialogOpen(true) + setPendingOtp(otp) + setStep(2) } const handleResend = async () => { @@ -133,8 +181,38 @@ export function ForgotPasswordForm({ className, ...props }) { setResendCooldown(30) } + // ── Step 3: New password ──────────────────────────────────────────────────── + const { + register: regPassword, + handleSubmit: submitPassword, + formState: { errors: errPassword, isSubmitting: isResetting }, + } = useForm({ + resolver: zodResolver(passwordSchema), + defaultValues: { new_password: '', confirm_password: '' }, + }) + + const onPasswordSubmit = async ({ new_password }) => { + const result = await resetPassword({ email: pendingEmail, otp: pendingOtp, new_password }) + + if (!result.success) { + setErrorMessage(result.message) + setErrorDialogOpen(true) + // The verified code may have expired/rotated by now — send them back to re-verify. + setStep(1) + return + } + + setSuccessDialogOpen(true) + } + return ( <> + +
{/* ── Step 1: Email ── */} {step === 0 && ( @@ -183,22 +261,21 @@ export function ForgotPasswordForm({ className, ...props }) { )} - {/* ── Step 2: OTP + new password ── */} + {/* ── Step 2: OTP ── */} {step === 1 && ( -
+
-

Reset your password.

+

Enter verification code.

We sent a 6-digit code to{' '} {pendingEmail}. - Enter it below along with your new password.

( )} /> - {errReset.otp && ( -

{errReset.otp.message}

+ {errOtp.otp && ( +

{errOtp.otp.message}

)}
@@ -236,6 +313,40 @@ export function ForgotPasswordForm({ className, ...props }) {
+ + + + + + + )} + + {/* ── Step 3: New password ── */} + {step === 2 && ( +
+
+

Choose a new password.

+

+ Your code has been verified. Set a new password for your account. +

+
+ {/* New password */}
@@ -247,7 +358,7 @@ export function ForgotPasswordForm({ className, ...props }) { autoComplete="new-password" disabled={isResetting} className="pr-10" - {...regReset('new_password')} + {...regPassword('new_password')} />
- {errReset.new_password && ( -

{errReset.new_password.message}

+ {errPassword.new_password && ( +

{errPassword.new_password.message}

)} @@ -275,7 +386,7 @@ export function ForgotPasswordForm({ className, ...props }) { autoComplete="new-password" disabled={isResetting} className="pr-10" - {...regReset('confirm_password')} + {...regPassword('confirm_password')} /> - {errReset.confirm_password && ( -

{errReset.confirm_password.message}

+ {errPassword.confirm_password && ( +

{errPassword.confirm_password.message}

)} @@ -309,7 +420,7 @@ export function ForgotPasswordForm({ className, ...props }) { type="button" variant="ghost" className="w-full text-muted-foreground" - onClick={() => setStep(0)} + onClick={() => setStep(1)} > ← Back @@ -330,6 +441,25 @@ export function ForgotPasswordForm({ className, ...props }) { + {/* Google Account Dialog */} + + + + This account signs in with Google + + We detected that this email is linked to an account created through Google Sign-In. + Accounts created this way don't have a password stored with us, so there's nothing + to reset here — sign in with Google instead to access your account. If you'd like a + password-based login too, you can set one from your account settings after signing in. + + + + setGoogleDialogOpen(false)}>Cancel + Sign in with Google + + + + {/* Success Dialog */} diff --git a/src/modules/auth/pages/ChangePassword.jsx b/src/modules/auth/pages/ChangePassword.jsx index aeb6b82..e89f415 100644 --- a/src/modules/auth/pages/ChangePassword.jsx +++ b/src/modules/auth/pages/ChangePassword.jsx @@ -94,12 +94,7 @@ export default function ChangePassword() { // Update local user state so must_change_password is cleared setUser((prev) => ({ ...prev, must_change_password: false })); - toast('Password changed successfully. Welcome!', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Password changed successfully. Welcome!'); // Redirect to the correct dashboard switch (user?.acc_type) { @@ -109,12 +104,7 @@ export default function ChangePassword() { default: navigate('/'); } } catch (err) { - toast(err?.response?.data?.message || 'Could not change password.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message || 'Could not change password.'); } }; diff --git a/src/modules/auth/pages/Intro.jsx b/src/modules/auth/pages/Intro.jsx index 206d308..5f6629d 100644 --- a/src/modules/auth/pages/Intro.jsx +++ b/src/modules/auth/pages/Intro.jsx @@ -121,13 +121,7 @@ export default function IntroPage() { navigate('/dashboard') } catch (err) { toast( - err?.response?.data?.message ?? 'Could not save your info. Please try again.', - { - action: { - label: "Close", - onClick: () => {} - } - } + err?.response?.data?.message ?? 'Could not save your info. Please try again.' ) } finally { setLoading(false) diff --git a/src/modules/auth/pages/OAuthCallback.jsx b/src/modules/auth/pages/OAuthCallback.jsx index 36e8317..bb1b6bc 100644 --- a/src/modules/auth/pages/OAuthCallback.jsx +++ b/src/modules/auth/pages/OAuthCallback.jsx @@ -3,40 +3,38 @@ * Type of Program: Frontend Page * Description: Landing page after the backend completes Google OIDC. * - * OTP path → backend confirmed the Google identity but, like every other - * login path, still gates on an OTP before issuing tokens. It - * redirects here with ?otpRequired=true&email= and has - * NOT set a refresh cookie yet. This page renders the shared - * OtpVerifyForm; once verified, AuthContext has user/tokens - * set and we navigate to the account's home route ourselves. + * The backend redirects here with NO query params — the outcome (otpRequired/ + * email, ban details, or an error code) never touches the URL. Instead, this + * page fetches it from GET /auth/google/result, a single-use endpoint that + * reads a short-lived signed cookie the callback controller set. * - * Trusted path → this device already cleared an OTP recently and its trust - * window is still valid. Backend redirects with - * ?otpRequired=false, having already set the refresh cookie — - * this page calls restoreSession() to pull the access token - * from it, then navigates to the account's home route. + * OTP path → result = { otpRequired: true, email }. No refresh cookie was + * set yet. Renders the shared OtpVerifyForm; once verified, + * AuthContext has user/tokens set and we navigate to the + * account's home route ourselves. * - * Error path → backend could not complete OIDC (state mismatch, token exchange - * failure, deactivated account, etc.). It redirected here with - * ?error=. No refresh cookie was set, so restoreSession() - * will fail and the user stays on this page to see the error. + * Trusted path → result = {} (nothing pending) — this device already cleared + * an OTP recently, so the backend set the real refresh cookie + * directly. This page calls restoreSession() to pull the + * access token from it, then navigates to the account's home + * route. * - * Fallback → neither param present (shouldn't normally happen now that the - * backend always redirects with one or the other) — falls back - * to the old spinner + restoreSession()/PublicRoute behavior. + * Error path → result = { error: , ... }. No refresh cookie was set, + * so the user stays on this page to see the error. * * Author: Kenneth Obsequio (@lash0000) * Date Created: Jun. 21, 2026 - * Date Modified: Jul. 5, 2026 — trusted-device OTP skip path + * Date Modified: Jul. 6, 2026 — outcome moved from URL params to /auth/google/result ***********************************************************************************************************************************************************************/ -import { useEffect } from 'react' -import { useSearchParams, Link, useNavigate } from 'react-router-dom' +import { useEffect, useState } from 'react' +import { Link, useNavigate } from 'react-router-dom' import { LoaderCircle, ShieldBan, UserX, AlertTriangle, RefreshCw, Clock } from 'lucide-react' import { Button } from '@/components/ui/button' import { useAuth } from '@/contexts/AuthContext' import { useDateFormat } from '@/hooks/useDateFormat' import { getRoleHomePath } from '@/utils/roleRedirect.util' import { OtpVerifyForm } from '@/modules/auth/components/OtpVerifyForm' +import api from '@/utils/api.util' const ERROR_MAP = { access_denied: { icon: UserX, message: 'You cancelled the Google sign-in.' }, @@ -47,33 +45,43 @@ const ERROR_MAP = { } export default function OAuthCallback() { - const [searchParams] = useSearchParams() const navigate = useNavigate() const { restoreSession } = useAuth() const { fmtDateTime } = useDateFormat() - const error = searchParams.get('error') - const otpRequiredParam = searchParams.get('otpRequired') - const otpRequired = otpRequiredParam === 'true' - const trusted = otpRequiredParam === 'false' - const email = searchParams.get('email') + + // null = still resolving; otherwise the parsed /auth/google/result payload. + const [result, setResult] = useState(null) useEffect(() => { - if (!trusted) return - restoreSession().then(({ success, user }) => { - navigate(success ? getRoleHomePath(user) : '/login', { replace: true }) - }) - }, [trusted]) + let cancelled = false - if (trusted) { - return ( -
-
- -

Signing you in...

-
-
- ) - } + ;(async () => { + let pending = {} + try { + const { data } = await api.get('/auth/google/result') + pending = data.data ?? {} + } catch { + // network hiccup — fall through to the trusted-device path below + } + + if (pending.otpRequired || pending.error) { + if (!cancelled) setResult(pending) + return + } + + // Nothing pending → trusted-device fast path. The refresh cookie was + // already set by the backend; restoreSession() just picks it up. + const { success, user } = await restoreSession() + if (cancelled) return + navigate(success ? getRoleHomePath(user) : '/login', { replace: true }) + })() + + return () => { cancelled = true } + }, []) + + const error = result?.error + const otpRequired = !!result?.otpRequired + const email = result?.email if (otpRequired && email) { return ( @@ -91,9 +99,9 @@ export default function OAuthCallback() { } if (error === 'account_banned') { - const reason = searchParams.get('reason') - const banType = searchParams.get('ban_type') - const expiresAt = searchParams.get('expires_at') + const reason = result.reason + const banType = result.ban_type + const expiresAt = result.expires_at const expiryText = expiresAt ? fmtDateTime(expiresAt) : null return ( diff --git a/src/modules/client/components/TaskListTable.jsx b/src/modules/client/components/TaskListTable.jsx index 2f7a8f3..60cad87 100644 --- a/src/modules/client/components/TaskListTable.jsx +++ b/src/modules/client/components/TaskListTable.jsx @@ -11,7 +11,7 @@ * loading {boolean} – show skeleton rows * onRefresh {function} – called when refresh button is clicked * onRowClick {function} – (taskList) => void, navigates to task list view - * statusLabel {string} – 'ongoing' | 'done' | 'overdue' — drives badge style + * statusLabel {string} – 'ongoing' | 'completed' | 'overdue' — drives badge style ***********************************************************************************************************************************************************************/ import { useState, useMemo } from 'react'; import { Input } from '@/components/ui/input'; @@ -35,9 +35,9 @@ const PAGE_SIZES = [50, 100, 1000]; // ─── Status badge per bucket ─────────────────────────────────────────────────── const StatusBadge = ({ status }) => { const map = { - ongoing: { label: 'Ongoing', icon: Circle, className: 'bg-muted text-muted-foreground' }, - done: { label: 'Done', icon: Check, className: 'bg-green-500/10 text-green-700 dark:text-green-400' }, - overdue: { label: 'Overdue', icon: AlertTriangle, className: 'bg-destructive/10 text-destructive' }, + ongoing: { label: 'Ongoing', icon: Circle, className: 'bg-muted text-muted-foreground' }, + completed: { label: 'Completed', icon: Check, className: 'bg-green-500/10 text-green-700 dark:text-green-400' }, + overdue: { label: 'Overdue', icon: AlertTriangle, className: 'bg-destructive/10 text-destructive' }, }; const { label, icon: Icon, className } = map[status] ?? map.ongoing; return ( diff --git a/src/modules/client/components/blocks/FileUpload.jsx b/src/modules/client/components/blocks/FileUpload.jsx index 92721d2..3d785af 100644 --- a/src/modules/client/components/blocks/FileUpload.jsx +++ b/src/modules/client/components/blocks/FileUpload.jsx @@ -180,13 +180,7 @@ const FileUpload = ({ if (rejected.length > 0) { setTimeout(() => toast( `${rejected.length === 1 ? `"${rejected[0]}" is` : `${rejected.length} files are`} not allowed. ` + - `Accepted types: ${allowed.join(", ")}.`, - { - action: { - label: "Close", - onClick: () => {} - } - } + `Accepted types: ${allowed.join(", ")}.` ), 0); } } @@ -197,25 +191,13 @@ const FileUpload = ({ const availableSlots = maxFileCount - prev.length; if (availableSlots <= 0) { setTimeout(() => toast( - `You can only attach up to ${maxFileCount} file${maxFileCount !== 1 ? "s" : ""}.`, - { - action: { - label: "Close", - onClick: () => {} - } - } + `You can only attach up to ${maxFileCount} file${maxFileCount !== 1 ? "s" : ""}.` ), 0); incoming = []; } else if (incoming.length > availableSlots) { setTimeout(() => toast( `Only ${availableSlots} more file${availableSlots !== 1 ? "s" : ""} can be added ` + - `(max ${maxFileCount}).`, - { - action: { - label: "Close", - onClick: () => {} - } - } + `(max ${maxFileCount}).` ), 0); incoming = incoming.slice(0, availableSlots); } @@ -242,12 +224,7 @@ const FileUpload = ({ if (duplicates.length > 0) { setTimeout(() => toast(duplicates.length === 1 ? `"${duplicates[0]}" is already attached.` - : `${duplicates.length} files are already attached.`, { - action: { - label: "Close", - onClick: () => {} - } - }), 0); + : `${duplicates.length} files are already attached.`), 0); } const next = prev.concat(toAdd); toAdd.forEach((e) => simulateUpload(e.id)); diff --git a/src/modules/client/components/blocks/ReadCourse.jsx b/src/modules/client/components/blocks/ReadCourse.jsx index 82e18b8..c1e3506 100644 --- a/src/modules/client/components/blocks/ReadCourse.jsx +++ b/src/modules/client/components/blocks/ReadCourse.jsx @@ -40,12 +40,7 @@ const ReadCourse = ({ title = "Read Course", courses = [], groupId, taskListId, courses.forEach((course) => { const prev = prevCompletedRef.current[course.id]; if (course.completed && prev === false) { - toast(`"${course.title}" has been automatically turned in!`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`"${course.title}" has been automatically turned in!`); } prevCompletedRef.current[course.id] = !!course.completed; }); diff --git a/src/modules/client/components/blocks/VisitLink.jsx b/src/modules/client/components/blocks/VisitLink.jsx index 82a8106..6739bfa 100644 --- a/src/modules/client/components/blocks/VisitLink.jsx +++ b/src/modules/client/components/blocks/VisitLink.jsx @@ -96,7 +96,7 @@ const LinkCard = ({ link, visited, onTurnIn, onUnvisit, submitting, unsubmitting footer={ <> - diff --git a/src/modules/client/layout/ClientLayout.jsx b/src/modules/client/layout/ClientLayout.jsx index 98ec9c9..4018908 100644 --- a/src/modules/client/layout/ClientLayout.jsx +++ b/src/modules/client/layout/ClientLayout.jsx @@ -1,6 +1,5 @@ import { Outlet, useMatches, useNavigate } from "react-router-dom" import { ThemeSwitcher } from "../components/ThemeSwitcher" -import { useTheme } from "@/contexts/ThemeContext" import { DropdownMenu, DropdownMenuContent, @@ -142,7 +141,6 @@ function getInitials(name = "") { function ClientNav() { const navigate = useNavigate() const { user, logout } = useAuth() - const { setTheme } = useTheme() // Background fetches only — nav rendering never waits on these const { achievements, getAchievements } = useProfile() @@ -214,7 +212,6 @@ function ClientNav() { const handleLogout = async () => { await logout() - setTheme('light') navigate("/login") } diff --git a/src/modules/client/pages/AccountSettings.jsx b/src/modules/client/pages/AccountSettings.jsx index 492502d..5910b26 100644 --- a/src/modules/client/pages/AccountSettings.jsx +++ b/src/modules/client/pages/AccountSettings.jsx @@ -63,21 +63,11 @@ function SecuritySection({ user, logout }) { const handleSubmit = async (e) => { e.preventDefault(); if (form.new_password !== form.confirm) { - toast("New passwords do not match.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("New passwords do not match."); return; } if (form.new_password.length < 8) { - toast("New password must be at least 8 characters.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("New password must be at least 8 characters."); return; } setLoading(true); @@ -86,23 +76,13 @@ function SecuritySection({ user, logout }) { current_password: form.current_password, new_password: form.new_password, }); - toast("Password changed. Logging you out…", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Password changed. Logging you out…"); setTimeout(async () => { await logout(); navigate("/login"); }, 1500); } catch (err) { - toast(err?.response?.data?.message ?? "Could not change password.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Could not change password."); } finally { setLoading(false); } @@ -288,11 +268,7 @@ function AdvertisementsSection() { const result = await updateProfile({ [key]: value }); if (result?.success) { toast("Preference saved.", { - description: "Reload the page for this to take effect.", - action: { - label: "Close", - onClick: () => {} - } + description: "Reload the page for this to take effect." }); } }; @@ -302,11 +278,7 @@ function AdvertisementsSection() { const result = await updateProfile({ show_popup_ads: false, show_other_ads: false }); if (result?.success) { toast("Preference saved.", { - description: "Reload the page for this to take effect.", - action: { - label: "Close", - onClick: () => {} - } + description: "Reload the page for this to take effect." }); } }; @@ -315,11 +287,7 @@ function AdvertisementsSection() { const result = await updateProfile({ show_popup_ads: !hide, show_other_ads: !hide }); if (result?.success) { toast("Preference saved.", { - description: "Reload the page for this to take effect.", - action: { - label: "Close", - onClick: () => {} - } + description: "Reload the page for this to take effect." }); } }; diff --git a/src/modules/client/pages/Checkout.jsx b/src/modules/client/pages/Checkout.jsx index d6df17f..5224868 100644 --- a/src/modules/client/pages/Checkout.jsx +++ b/src/modules/client/pages/Checkout.jsx @@ -122,12 +122,7 @@ const Checkout = () => { if (!wasCancelled) return; const orderId = searchParams.get("token"); if (orderId) cancelOrder(orderId); - toast("PayPal checkout was cancelled.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("PayPal checkout was cancelled."); navigate(`/plans/checkout?plan_id=${planId}`, { replace: true }); }, [wasCancelled]); // eslint-disable-line react-hooks/exhaustive-deps @@ -158,19 +153,9 @@ const Checkout = () => { const result = await validatePromo(plan.plan_id, promoCode.trim().toUpperCase()); if (result?.valid) { setPromoResult(result); - toast("Promo code applied.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Promo code applied."); } else { - toast(result?.reason ?? "Invalid promo code.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(result?.reason ?? "Invalid promo code."); } }; @@ -186,12 +171,7 @@ const Checkout = () => { ); if (!order) return; const approvalUrl = order.approval_url; - if (!approvalUrl) { toast("Could not get PayPal approval URL.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; } + if (!approvalUrl) { toast("Could not get PayPal approval URL."); return; } window.location.href = approvalUrl; }; diff --git a/src/modules/client/pages/CourseCheckout.jsx b/src/modules/client/pages/CourseCheckout.jsx index c7a4e28..def2dc5 100644 --- a/src/modules/client/pages/CourseCheckout.jsx +++ b/src/modules/client/pages/CourseCheckout.jsx @@ -68,12 +68,7 @@ export default function CourseCheckout() { if (!wasCancelled) return; const orderId = searchParams.get("token"); if (orderId) cancelCourseOrder(orderId); - toast("Payment was cancelled.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Payment was cancelled."); navigate(`/course/${courseId}/checkout`, { replace: true }); }, [wasCancelled]); // eslint-disable-line react-hooks/exhaustive-deps @@ -81,12 +76,7 @@ export default function CourseCheckout() { if (!course?.product?.id) return; const order = await createCourseOrder(course.product.id); if (!order) return; - if (!order.approval_url) { toast("Could not get PayPal approval URL.", { - action: { - label: "Close", - onClick: () => {} - } - }); return; } + if (!order.approval_url) { toast("Could not get PayPal approval URL."); return; } window.location.href = order.approval_url; }; diff --git a/src/modules/client/pages/CourseDetails.jsx b/src/modules/client/pages/CourseDetails.jsx index 55e15e8..7f1848c 100644 --- a/src/modules/client/pages/CourseDetails.jsx +++ b/src/modules/client/pages/CourseDetails.jsx @@ -30,6 +30,7 @@ import { resolveTierBadge } from "@/utils/tierBadge.util"; import { useClientAdvertisements } from "@/contexts/ClientAdvertisementContext"; import { Banner, BannerSkeleton } from "@/components/generic/Blocks/Client/Advertisements/Banner"; import { Sidebar, SidebarSkeleton } from "@/components/generic/Blocks/Client/Advertisements/Sidebar"; +import { Tags } from "lucide-react"; // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -44,8 +45,6 @@ function formatDuration(seconds = 0) { // ─── Spine / card helpers ────────────────────────────────────────────────────── -const INTRO_HEIGHT = 50; - const useVisibleNodes = (refs, count) => { const [visible, setVisible] = useState(new Set()); useEffect(() => { @@ -131,15 +130,15 @@ const UnitAccordionBlock = ({ unit, unitIndex, i, cardRefs, courseId, onToggle, className="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-slate-200 dark:hover:bg-blue-500 transition-colors cursor-pointer" onClick={() => navigate(`/course/${courseId}/unit`, { state: { lessonId: lesson.lesson_id, unitId: unit.unit_id } })} > -
+
{isCompleted(lesson.uuid) ? :
} - {lesson.title} + {lesson.title}
{lesson.duration_seconds > 0 && ( - {formatDuration(lesson.duration_seconds)} + {formatDuration(lesson.duration_seconds)} )}
))} @@ -150,17 +149,19 @@ const UnitAccordionBlock = ({ unit, unitIndex, i, cardRefs, courseId, onToggle, className="flex items-center justify-between py-2 px-3 mt-1 rounded-lg border border-dashed border-blue-300 dark:border-blue-700 bg-blue-50/60 dark:bg-blue-950/30 hover:bg-blue-100 dark:hover:bg-blue-900/40 transition-colors cursor-pointer" onClick={() => navigate(`/course/${courseId}/unit`, { state: { quizUnitId: unit.unit_id } })} > -
+
{quiz.has_passed ? : } - {quiz.title} + {quiz.title}
- + {quiz.has_passed ? "Passed" : "Quiz"}
@@ -246,7 +247,7 @@ const CertCard = ({ courseTitle, courseLevel, badgeColor, badgeImageUrl, pending +
{/* Spine */} -
+
{mids.length > 0 && ( <> {/* Cards */} -
+
{nodes.map((node, ni) => { const delay = ni * 0.05; const nodeRef = (el) => (cardRefs.current[ni] = el); @@ -566,12 +567,7 @@ const CourseDetails = () => { }, [course?.badge_asset_id, course?.badge_image_url]); if (courseBlocked) { - toast("You don't have access to this course. Upgrade your plan.", { - action: { - label: "Close", - onClick: () => { } - } - }); + toast("You don't have access to this course. Upgrade your plan."); navigate("/course", { replace: true }); return null; } @@ -616,12 +612,20 @@ const CourseDetails = () => { Coming Soon ) : ( - + <> + + + )}
@@ -648,15 +652,20 @@ const CourseDetails = () => {
-
+
{(() => { const slug = course?.plan_tier ?? course?.subscription ?? "free"; const { label, cls } = resolveTierBadge(slug, tierMap); return {label}; })()} + {(course?.categories ?? []).map((cat) => ( + + {cat.name} + + ))}
-

{course?.title ?? "Course Title"}

-

{course?.description ?? ""}

+

{course?.title ?? "Course Title"}

+

{course?.description ?? ""}

{course?.duration_seconds > 0 && (
@@ -704,9 +713,9 @@ const CourseDetails = () => {
{/* Body */} -
+
-
+
About this course
diff --git a/src/modules/client/pages/CourseList.jsx b/src/modules/client/pages/CourseList.jsx index a11e2e7..6cf681c 100644 --- a/src/modules/client/pages/CourseList.jsx +++ b/src/modules/client/pages/CourseList.jsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { House, Timer, ChevronLeft, ChevronRight, LockIcon, Tag, Check, ShoppingCart } from "lucide-react"; +import { House, Timer, ChevronLeft, ChevronRight, LockIcon, Tag, Tags, Check, ShoppingCart } from "lucide-react"; import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb"; import { Input } from "@/components/ui/input"; import { @@ -20,6 +20,7 @@ import { resolveTierBadge } from "@/utils/tierBadge.util"; import { Building2 } from "lucide-react"; import { useClientAdvertisements } from "@/contexts/ClientAdvertisementContext"; import { Banner, BannerSkeleton } from "@/components/generic/Blocks/Client/Advertisements/Banner"; +import { GitBranch } from "lucide-react"; // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -37,8 +38,8 @@ const ITEMS_PER_PAGE = 10; // ─── Course Card ────────────────────────────────────────────────────────────── const CourseCard = ({ course, tierMap, onViewDetails }) => { - const slug = course.subscription ?? "free"; - const locked = course.is_locked; + const slug = course.subscription ?? "free"; + const locked = course.is_locked; const duration = formatDuration(course.duration_seconds); const { rank, label, cls } = resolveTierBadge(slug, tierMap); @@ -58,11 +59,14 @@ const CourseCard = ({ course, tierMap, onViewDetails }) => { {locked ? : } {label} - {course.level && ( - {course.level.charAt(0).toUpperCase() + course.level.slice(1)} + {course.level && !locked && ( + {course.level.charAt(0).toUpperCase() + course.level.slice(1)} )} + {(course.categories ?? []).map((cat) => ( + {cat.name} + ))} {locked && ( - + Locked )} @@ -113,7 +117,7 @@ const CourseCardSkeleton = () => ( const Pagination = ({ currentPage, totalPages, totalItems, itemsPerPage, onPageChange }) => { const start = (currentPage - 1) * itemsPerPage + 1; - const end = Math.min(currentPage * itemsPerPage, totalItems); + const end = Math.min(currentPage * itemsPerPage, totalItems); const getPages = () => { const pages = []; @@ -165,12 +169,12 @@ const CoursesList = () => { const { advertisements, loading: adLoading, getActiveAdvertisement, handleAdCtaClick } = useClientAdvertisements(); const [tierCategories, setTierCategories] = useState([]); - const [currentPage, setCurrentPage] = useState(1); - const [search, setSearch] = useState(""); - const [levelFilter, setLevelFilter] = useState("All"); - const [subFilter, setSubFilter] = useState("All"); + const [currentPage, setCurrentPage] = useState(1); + const [search, setSearch] = useState(""); + const [levelFilter, setLevelFilter] = useState("All"); + const [subFilter, setSubFilter] = useState("All"); const [categoryFilter, setCategoryFilter] = useState("All"); - const [modalOpen, setModalOpen] = useState(false); + const [modalOpen, setModalOpen] = useState(false); const [selectedCourse, setSelectedCourse] = useState(null); const [allCategories, setAllCategories] = useState([]); @@ -179,10 +183,10 @@ const CoursesList = () => { getCourses(); api.get("/client/tiers/categories") .then(({ data }) => setTierCategories(data.data ?? [])) - .catch(() => {}); + .catch(() => { }); api.get("/client/courses/categories") .then(({ data }) => setAllCategories(data.data ?? [])) - .catch(() => {}); + .catch(() => { }); getActiveAdvertisement("course_list.banner"); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -199,10 +203,10 @@ const CoursesList = () => { const filtered = useMemo(() => courses .filter((c) => { - const matchSearch = c.title.toLowerCase().includes(search.toLowerCase()) || + const matchSearch = c.title.toLowerCase().includes(search.toLowerCase()) || (c.description ?? "").toLowerCase().includes(search.toLowerCase()); - const matchLevel = levelFilter === "All" || (c.level ?? "").toLowerCase() === levelFilter.toLowerCase(); - const matchSub = subFilter === "All" || c.subscription === subFilter; + const matchLevel = levelFilter === "All" || (c.level ?? "").toLowerCase() === levelFilter.toLowerCase(); + const matchSub = subFilter === "All" || c.subscription === subFilter; const matchCategory = categoryFilter === "All" || (c.categories ?? []).some((cat) => String(cat.id) === categoryFilter); return matchSearch && matchLevel && matchSub && matchCategory; }) @@ -211,7 +215,7 @@ const CoursesList = () => { ); const totalPages = Math.max(1, Math.ceil(filtered.length / ITEMS_PER_PAGE)); - const paginated = filtered.slice((currentPage - 1) * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE); + const paginated = filtered.slice((currentPage - 1) * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE); const handleViewDetails = (course) => { if (course.is_locked) { @@ -299,6 +303,25 @@ const CoursesList = () => { )} + {categoryFilter !== "All" && ( +
+ Tags: + {allCategories + .filter((cat) => String(cat.id) === categoryFilter) + .map((cat) => ( + { setCategoryFilter("All"); setCurrentPage(1); }} + > + + {cat.name} + + ))} +
+ )} + {/* Course Grid */} {coursesLoading ? (
diff --git a/src/modules/client/pages/Dashboard.jsx b/src/modules/client/pages/Dashboard.jsx index ff1dffc..1d7c8b5 100644 --- a/src/modules/client/pages/Dashboard.jsx +++ b/src/modules/client/pages/Dashboard.jsx @@ -229,10 +229,6 @@ const Client = () => { toast('Welcome to Philproperties!', { description: 'You earned the Early Access badge. Check your notifications for details.', duration: 6000, - action: { - label: "Close", - onClick: () => {} - } }); window.history.replaceState({}, ''); }, []); diff --git a/src/modules/client/pages/MyCertificates.jsx b/src/modules/client/pages/MyCertificates.jsx index d1fba61..8351e7b 100644 --- a/src/modules/client/pages/MyCertificates.jsx +++ b/src/modules/client/pages/MyCertificates.jsx @@ -30,12 +30,7 @@ const CertificateCard = ({ courseTitle, issuedAt, courseUuid, badgeColor, badgeI a.click(); URL.revokeObjectURL(url); } catch { - toast("Could not download certificate.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Could not download certificate."); } finally { setDownloading(false); } diff --git a/src/modules/client/pages/Notifications.jsx b/src/modules/client/pages/Notifications.jsx index 36d1ca0..0d85a36 100644 --- a/src/modules/client/pages/Notifications.jsx +++ b/src/modules/client/pages/Notifications.jsx @@ -141,18 +141,8 @@ export default function Notifications() { const ok = await clearAll(); setClearing(false); setClearOpen(false); - if (ok) toast("All notifications cleared.", { - action: { - label: "Close", - onClick: () => {} - } - }); - else toast("Could not clear notifications.", { - action: { - label: "Close", - onClick: () => {} - } - }); + if (ok) toast("All notifications cleared."); + else toast("Could not clear notifications."); } const rangeStart = pagination.total === 0 ? 0 : (pagination.page - 1) * pagination.limit + 1; diff --git a/src/modules/client/pages/PlanList.jsx b/src/modules/client/pages/PlanList.jsx index 6c1e737..121541b 100644 --- a/src/modules/client/pages/PlanList.jsx +++ b/src/modules/client/pages/PlanList.jsx @@ -385,22 +385,12 @@ export default function PlanList() { setRefundLoading(true); try { const { data } = await api.post("/client/tiers/checkout/refund"); - toast(data.message ?? "Refund processed. Your access has been revoked.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(data.message ?? "Refund processed. Your access has been revoked."); setRefundPlan(null); resetMyTier(); getMyTier(); } catch (err) { - toast(err?.response?.data?.message ?? "Refund failed. Please try again.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(err?.response?.data?.message ?? "Refund failed. Please try again."); } finally { setRefundLoading(false); } diff --git a/src/modules/client/pages/Profile.jsx b/src/modules/client/pages/Profile.jsx index 3803987..956a09c 100644 --- a/src/modules/client/pages/Profile.jsx +++ b/src/modules/client/pages/Profile.jsx @@ -134,12 +134,7 @@ const LandscapeCertCard = ({ courseTitle, issuedAt, courseUuid, badgeColor, badg a.click(); URL.revokeObjectURL(url); } catch { - toast("Could not download certificate.", { - action: { - label: "Close", - onClick: () => {} - } - }); + toast("Could not download certificate."); } finally { setDownloading(false); } @@ -525,16 +520,22 @@ const ProfilePage = () => { } > {pendingModalCourse && (() => { - const { pending_quizzes = [], pending_assessment } = pendingModalCourse; + const { pending_quizzes = [], pending_assessment, assessment_configured = true } = pendingModalCourse; const hasQuizzes = pending_quizzes.length > 0; const hasAssessment = !!pending_assessment; - if (!hasQuizzes && !hasAssessment) { + if (!hasQuizzes && !hasAssessment && assessment_configured) { return (

No pending items found.

); } return (
+ {!assessment_configured && ( +

+ You've finished all lessons, but this course's final assessment hasn't been set up yet. + Check back once it's available — the course can't be marked complete until then. +

+ )} {hasQuizzes && (

Unit Quizzes

diff --git a/src/modules/client/pages/UnitList.jsx b/src/modules/client/pages/UnitList.jsx index 4da5602..bb4e3db 100644 --- a/src/modules/client/pages/UnitList.jsx +++ b/src/modules/client/pages/UnitList.jsx @@ -507,12 +507,7 @@ const UnitList = () => { useEffect(() => { if (!completedTasks.length) return; completedTasks.forEach((t) => { - toast(`"${t.task_name}" automatically turned in!`, { - action: { - label: "Close", - onClick: () => {} - } - }); + toast(`"${t.task_name}" automatically turned in!`); }); clearCompletedTasks(); }, [completedTasks]); @@ -822,7 +817,7 @@ const UnitList = () => { {/* ── Task-mode banner ─────────────────────────────────────────── */} {taskCtx?.has_task && ( -
@@ -976,7 +971,7 @@ const UnitList = () => {
{/* ── Main content ── */} -
+
{selectedCompletion ? ( diff --git a/src/modules/client/pages/ViewTask.jsx b/src/modules/client/pages/ViewTask.jsx index 3494d41..6b48202 100644 --- a/src/modules/client/pages/ViewTask.jsx +++ b/src/modules/client/pages/ViewTask.jsx @@ -315,12 +315,7 @@ const ViewTask = () => { } if (!uploadedFiles.length) { - toast('No files were uploaded successfully.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('No files were uploaded successfully.'); return; } @@ -334,12 +329,7 @@ const ViewTask = () => { setNote(''); setUploadState({ files: [], isUploading: false }); } catch (err) { - toast('Failed to submit. Please try again.', { - action: { - label: "Close", - onClick: () => {} - } - }); + toast('Failed to submit. Please try again.'); } finally { setSubmitting(false); } @@ -413,7 +403,7 @@ const ViewTask = () => {
{/* ── Left ──────────────────────────────────────────────── */} -
+
{/* Task header card */}
@@ -448,6 +438,18 @@ const ViewTask = () => {
+ {/* Requirements status panel — mobile only, right after the header */} + {!isResolving && requirements.length > 0 && ( +
+ +
+ )} + {/* Requirements section */} {!isResolving && requirements.length > 0 && ( <> @@ -519,10 +521,22 @@ const ViewTask = () => { )} )} + + {/* Your work — mobile only, at the very end */} + {hasFileUpload && ( +
+ setTaskModal(true)} + submitting={submitting} + onFileClick={(file) => setPreviewFile(file)} + /> +
+ )}
- {/* ── Right ─────────────────────────────────────────────── */} -
+ {/* ── Right (desktop sidebar only) ───────────────────────── */} +
{hasFileUpload && ( { return ( - Done + Completed ); } @@ -282,9 +282,9 @@ const ViewTaskDetails = () => { {renderTab('No ongoing tasks right now.', 'ongoing')} - {/* ── Done ── */} - - {renderTab('No completed tasks yet.', 'done')} + {/* ── Completed ── */} + + {renderTab('No completed tasks yet.', 'completed')} {/* ── Overdue ── */} diff --git a/src/modules/public/pages/LandingPage.jsx b/src/modules/public/pages/LandingPage.jsx index 2cf2aef..c262a04 100644 --- a/src/modules/public/pages/LandingPage.jsx +++ b/src/modules/public/pages/LandingPage.jsx @@ -205,12 +205,7 @@ function LandingPage() {