From 75693453da035b278e4313b6429c2f83183a44ff Mon Sep 17 00:00:00 2001 From: Timofey Obraztsov Date: Wed, 30 Oct 2024 12:26:30 -0700 Subject: [PATCH 1/2] feat: toastify instead of alerts --- pnpm-lock.yaml | 16 ++++++++++++++ site/package.json | 2 ++ site/src/App.tsx | 2 +- site/src/component/Review/SubReview.tsx | 6 +++--- site/src/component/ReviewForm/ReviewForm.tsx | 13 ++++++------ site/src/hooks/toastifyHook.tsx | 14 +++++++++++++ site/src/pages/RoadmapPage/Planner.tsx | 9 ++++---- .../pages/RoadmapPage/RoadmapMultiplan.tsx | 12 +++++------ site/src/store/slices/roadmapSlice.ts | 21 ++++++++++--------- 9 files changed, 65 insertions(+), 30 deletions(-) create mode 100644 site/src/hooks/toastifyHook.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d90f275..e72a266c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,6 +208,9 @@ importers: semantic-ui-react: specifier: ^2.1.5 version: 2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + toastify-js: + specifier: ^1.12.0 + version: 1.12.0 websoc-fuzzy-search: specifier: 1.0.1 version: 1.0.1 @@ -233,6 +236,9 @@ importers: '@types/react-twemoji': specifier: ^0.4.3 version: 0.4.3 + '@types/toastify-js': + specifier: ^1.12.3 + version: 1.12.3 '@typescript-eslint/eslint-plugin': specifier: ^7.8.0 version: 7.8.0(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) @@ -2095,6 +2101,9 @@ packages: '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + '@types/toastify-js@1.12.3': + resolution: {integrity: sha512-9RjLlbAHMSaae/KZNHGv19VG4gcLIm3YjvacCXBtfMfYn26h76YP5oxXI8k26q4iKXCB9LNfv18lsoS0JnFPTg==} + '@types/use-sync-external-store@0.0.3': resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} @@ -4788,6 +4797,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toastify-js@1.12.0: + resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -8414,6 +8426,8 @@ snapshots: '@types/node': 20.12.8 '@types/send': 0.17.4 + '@types/toastify-js@1.12.3': {} + '@types/use-sync-external-store@0.0.3': {} '@types/warning@3.0.3': {} @@ -11724,6 +11738,8 @@ snapshots: dependencies: is-number: 7.0.0 + toastify-js@1.12.0: {} + toidentifier@1.0.1: {} touch@3.1.1: {} diff --git a/site/package.json b/site/package.json index 1d038a05..09b07cec 100644 --- a/site/package.json +++ b/site/package.json @@ -28,6 +28,7 @@ "react-twemoji": "^0.5.0", "semantic-ui-css": "^2.5.0", "semantic-ui-react": "^2.1.5", + "toastify-js": "^1.12.0", "websoc-fuzzy-search": "1.0.1" }, "scripts": { @@ -57,6 +58,7 @@ "@types/react-google-recaptcha": "^2.1.9", "@types/react-transition-group": "^4.4.10", "@types/react-twemoji": "^0.4.3", + "@types/toastify-js": "^1.12.3", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "@vitejs/plugin-react": "^4.2.1", diff --git a/site/src/App.tsx b/site/src/App.tsx index 6267352c..864d6456 100644 --- a/site/src/App.tsx +++ b/site/src/App.tsx @@ -5,7 +5,7 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import 'react-bootstrap-range-slider/dist/react-bootstrap-range-slider.css'; import './style/theme.scss'; import './App.scss'; - +import 'toastify-js/src/toastify.css'; import AppHeader from './component/AppHeader/AppHeader'; import ChangelogModal from './component/ChangelogModal/ChangelogModal'; import SearchPage from './pages/SearchPage'; diff --git a/site/src/component/Review/SubReview.tsx b/site/src/component/Review/SubReview.tsx index c5790f33..a16b991c 100644 --- a/site/src/component/Review/SubReview.tsx +++ b/site/src/component/Review/SubReview.tsx @@ -15,7 +15,7 @@ import ThemeContext from '../../style/theme-context'; import ReviewForm from '../ReviewForm/ReviewForm'; import trpc from '../../trpc'; import { ReviewData, VoteRequest } from '@peterportal/types'; - +import useToastify from '../../hooks/toastifyHook'; interface SubReviewProps { review: ReviewData; course?: CourseGQLData; @@ -31,7 +31,7 @@ const SubReview: FC = ({ review, course, professor }) => { const buttonVariant = darkMode ? 'dark' : 'secondary'; const [showDeleteModal, setShowDeleteModal] = useState(false); const [showReviewForm, setShowReviewForm] = useState(false); - + const toastify = useToastify(); const sendVote = async (voteReq: VoteRequest) => { const { deltaScore } = await trpc.reviews.vote.mutate(voteReq); return deltaScore; @@ -63,7 +63,7 @@ const SubReview: FC = ({ review, course, professor }) => { const upvote = async () => { if (cookies.user === undefined) { - alert('You must be logged in to vote.'); + toastify('You must be logged in to vote.'); return; } diff --git a/site/src/component/ReviewForm/ReviewForm.tsx b/site/src/component/ReviewForm/ReviewForm.tsx index d20ae0c3..4b784071 100644 --- a/site/src/component/ReviewForm/ReviewForm.tsx +++ b/site/src/component/ReviewForm/ReviewForm.tsx @@ -25,6 +25,7 @@ import { ReviewTags, tags, } from '@peterportal/types'; +import toaster from '../../hooks/toastifyHook'; interface ReviewFormProps extends ReviewProps { closeForm: () => void; @@ -62,13 +63,13 @@ const ReviewForm: FC = ({ const [userName, setUserName] = useState(reviewToEdit?.userDisplay ?? cookies.user?.name); const [validated, setValidated] = useState(false); const { darkMode } = useContext(ThemeContext); - + const toastify = toaster(); useEffect(() => { if (show) { // form opened // if not logged in, close the form if (cookies.user === undefined) { - alert('You must be logged in to add a review!'); + toastify('You must be logged in to add a review!'); closeForm(); } @@ -86,7 +87,7 @@ const ReviewForm: FC = ({ setSubmitted(true); dispatch(editReview(res)); } catch (e) { - alert((e as Error).message); + toastify((e as Error).message); } } else { try { @@ -94,7 +95,7 @@ const ReviewForm: FC = ({ setSubmitted(true); dispatch(addReview(res)); } catch (e) { - alert((e as Error).message); + toastify((e as Error).message); } } }; @@ -115,7 +116,7 @@ const ReviewForm: FC = ({ } // check if CAPTCHA is completed for new reviews (captcha omitted for editing) if (!editing && !captchaToken) { - alert('Please complete the CAPTCHA'); + toastify('Please complete the CAPTCHA'); return; } const review = { @@ -154,7 +155,7 @@ const ReviewForm: FC = ({ newSelectedTags.push(tag); setSelectedTags(newSelectedTags); } else { - alert('Cannot select more than 3 tags'); + toastify('Cannot select more than 3 tags'); } } }; diff --git a/site/src/hooks/toastifyHook.tsx b/site/src/hooks/toastifyHook.tsx new file mode 100644 index 00000000..69a2bc1a --- /dev/null +++ b/site/src/hooks/toastifyHook.tsx @@ -0,0 +1,14 @@ +import Toastify from 'toastify-js'; + +export default function toaster() { + return (text: string, style?: Record, callback?: () => void) => + Toastify({ + text, + duration: 3000, + close: true, + gravity: 'bottom', + position: 'right', + style: { background: 'var(--peterportal-primary-color-1)', fontWeight: 'bold', ...style }, + onClick: callback, + }).showToast(); +} diff --git a/site/src/pages/RoadmapPage/Planner.tsx b/site/src/pages/RoadmapPage/Planner.tsx index 9194fe21..79e255e4 100644 --- a/site/src/pages/RoadmapPage/Planner.tsx +++ b/site/src/pages/RoadmapPage/Planner.tsx @@ -22,6 +22,7 @@ import ImportTranscriptPopup from './ImportTranscriptPopup'; import { collapseAllPlanners, loadRoadmap, validatePlanner } from '../../helpers/planner'; import { Button, Modal } from 'react-bootstrap'; import trpc from '../../trpc'; +import toaster from '../../hooks/toastifyHook'; const Planner: FC = () => { const dispatch = useAppDispatch(); @@ -32,7 +33,7 @@ const Planner: FC = () => { const transfers = useAppSelector((state) => state.roadmap.transfers); const coursebag = useAppSelector((state) => state.roadmap.coursebag); const [showSyncModal, setShowSyncModal] = useState(false); - + const toastify = toaster(); const [missingPrerequisites, setMissingPrerequisites] = useState(new Set()); const roadmapStr = JSON.stringify({ planners: collapseAllPlanners(allPlanData), @@ -71,13 +72,13 @@ const Planner: FC = () => { trpc.roadmaps.save .mutate(mongoRoadmap) .then(() => { - alert(`Roadmap saved under ${cookies.user.email}`); + toastify(`Roadmap saved under ${cookies.user.email}`); }) .catch(() => { - alert('Roadmap saved locally! Login to save it to your account.'); + toastify('Roadmap saved locally! Login to save it to your account.'); }); } else { - alert('Roadmap saved locally! Login to save it to your account.'); + toastify('Roadmap saved locally! Login to save it to your account.'); } }; diff --git a/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx b/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx index a9692e32..d3b1a2e5 100644 --- a/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx +++ b/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx @@ -14,7 +14,7 @@ import './RoadmapMultiplan.scss'; import * as Icon from 'react-bootstrap-icons'; import { Button } from 'semantic-ui-react'; import { Button as Button2, Form, Modal } from 'react-bootstrap'; - +import toaster from '../../hooks/toastifyHook'; interface RoadmapSelectableItemProps { plan: RoadmapPlan; index: number; @@ -54,7 +54,7 @@ const RoadmapMultiplan: FC = () => { const [delIdx, setDelIdx] = useState(-1); const [newPlanName, setNewPlanName] = useState(allPlans.plans[allPlans.currentPlanIndex].name); const [showDropdown, setShowDropdown] = useState(false); - + const toastify = toaster(); const isDuplicateName = () => allPlans.plans.find((p) => p.name === newPlanName); // name: name of the plan, content: stores the content of plan @@ -74,8 +74,8 @@ const RoadmapMultiplan: FC = () => { }; const handleSubmitNewPlan = () => { - if (!newPlanName) return alert('Name cannot be empty'); - if (isDuplicateName()) return alert('A plan with that name already exists'); + if (!newPlanName) return toastify('Name cannot be empty'); + if (isDuplicateName()) return toastify('A plan with that name already exists'); setIsOpen(false); addNewPlan(newPlanName); const newIndex = allPlans.plans.length; @@ -84,8 +84,8 @@ const RoadmapMultiplan: FC = () => { }; const modifyPlanName = () => { - if (!newPlanName) return alert('Name cannot be empty'); - if (isDuplicateName()) return alert('A plan with that name already exists'); + if (!newPlanName) return toastify('Name cannot be empty'); + if (isDuplicateName()) return toastify('A plan with that name already exists'); dispatch(setPlanName({ index: editIdx, name: newPlanName })); setEditIdx(-1); }; diff --git a/site/src/store/slices/roadmapSlice.ts b/site/src/store/slices/roadmapSlice.ts index 3ba4705c..3fa1c331 100644 --- a/site/src/store/slices/roadmapSlice.ts +++ b/site/src/store/slices/roadmapSlice.ts @@ -13,7 +13,7 @@ import { } from '../../types/types'; import type { RootState } from '../store'; import { TransferData } from '@peterportal/types'; - +import toaster from '../../hooks/toastifyHook'; // Define a type for the slice state interface RoadmapPlanState { // Store planner data @@ -169,8 +169,9 @@ export const roadmapSlice = createSlice({ const newQuarter = action.payload.quarterData; // if year doesn't exist + const toastify = toaster(); if (!currentYears.includes(startYear)) { - alert(`${startYear}-${startYear + 1} has not yet been added!`); + toastify(`${startYear}-${startYear + 1} has not yet been added!`); return; } @@ -181,7 +182,7 @@ export const roadmapSlice = createSlice({ // if duplicate quarter if (currentQuarters.includes(newQuarter.name)) { - alert(`${quarterDisplayNames[newQuarter.name]} has already been added to Year ${yearIndex}!`); + toastify(`${quarterDisplayNames[newQuarter.name]} has already been added to Year ${yearIndex}!`); return; } @@ -218,16 +219,16 @@ export const roadmapSlice = createSlice({ const newYear = action.payload.yearData.startYear; const currentNames = state.plans[state.currentPlanIndex].content.yearPlans.map((e) => e.name); const newName = action.payload.yearData.name; - + const toastify = toaster(); // if duplicate year if (currentYears.includes(newYear)) { - alert(`${newYear}-${newYear + 1} has already been added as Year ${currentYears.indexOf(newYear) + 1}!`); + toastify(`${newYear}-${newYear + 1} has already been added as Year ${currentYears.indexOf(newYear) + 1}!`); return; } // if duplicate name if (currentNames.includes(newName)) { const year = state.plans[state.currentPlanIndex].content.yearPlans[currentNames.indexOf(newName)].startYear; - alert(`${newName} already exists from ${year} - ${year + 1}!`); + toastify(`${newName} already exists from ${year} - ${year + 1}!`); return; } @@ -246,10 +247,10 @@ export const roadmapSlice = createSlice({ const currentYears = state.plans[state.currentPlanIndex].content.yearPlans.map((e) => e.startYear); const newYear = action.payload.startYear; const yearIndex = action.payload.index; - + const toastify = toaster(); // if duplicate year if (currentYears.includes(newYear)) { - alert(`${newYear}-${newYear + 1} already exists as Year ${currentYears.indexOf(newYear) + 1}!`); + toastify(`${newYear}-${newYear + 1} already exists as Year ${currentYears.indexOf(newYear) + 1}!`); return; } @@ -273,11 +274,11 @@ export const roadmapSlice = createSlice({ const currentNames = state.plans[state.currentPlanIndex].content.yearPlans.map((e) => e.name); const newName = action.payload.name; const yearIndex = action.payload.index; - + const toastify = toaster(); // if duplicate name if (currentNames.includes(newName)) { const year = state.plans[state.currentPlanIndex].content.yearPlans[yearIndex].startYear; - alert(`${newName} already exists from ${year} - ${year + 1}!`); + toastify(`${newName} already exists from ${year} - ${year + 1}!`); return; } From 28f03ed510acbf067383164bd5af24d4a3a03296 Mon Sep 17 00:00:00 2001 From: Timofey Obraztsov Date: Wed, 6 Nov 2024 12:23:46 -0800 Subject: [PATCH 2/2] style: make error toasts red --- site/src/component/Review/SubReview.tsx | 5 ++--- site/src/component/ReviewForm/ReviewForm.tsx | 13 ++++++------ site/src/helpers/toastify.ts | 17 +++++++++++++++ site/src/hooks/toastifyHook.tsx | 14 ------------- site/src/pages/RoadmapPage/Planner.tsx | 9 ++++---- .../pages/RoadmapPage/RoadmapMultiplan.tsx | 11 +++++----- site/src/store/slices/roadmapSlice.ts | 21 +++++++++---------- 7 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 site/src/helpers/toastify.ts delete mode 100644 site/src/hooks/toastifyHook.tsx diff --git a/site/src/component/Review/SubReview.tsx b/site/src/component/Review/SubReview.tsx index a16b991c..d0321981 100644 --- a/site/src/component/Review/SubReview.tsx +++ b/site/src/component/Review/SubReview.tsx @@ -15,7 +15,7 @@ import ThemeContext from '../../style/theme-context'; import ReviewForm from '../ReviewForm/ReviewForm'; import trpc from '../../trpc'; import { ReviewData, VoteRequest } from '@peterportal/types'; -import useToastify from '../../hooks/toastifyHook'; +import spawnToast from '../../helpers/toastify'; interface SubReviewProps { review: ReviewData; course?: CourseGQLData; @@ -31,7 +31,6 @@ const SubReview: FC = ({ review, course, professor }) => { const buttonVariant = darkMode ? 'dark' : 'secondary'; const [showDeleteModal, setShowDeleteModal] = useState(false); const [showReviewForm, setShowReviewForm] = useState(false); - const toastify = useToastify(); const sendVote = async (voteReq: VoteRequest) => { const { deltaScore } = await trpc.reviews.vote.mutate(voteReq); return deltaScore; @@ -63,7 +62,7 @@ const SubReview: FC = ({ review, course, professor }) => { const upvote = async () => { if (cookies.user === undefined) { - toastify('You must be logged in to vote.'); + spawnToast('You must be logged in to vote.', true); return; } diff --git a/site/src/component/ReviewForm/ReviewForm.tsx b/site/src/component/ReviewForm/ReviewForm.tsx index 4b784071..869562f8 100644 --- a/site/src/component/ReviewForm/ReviewForm.tsx +++ b/site/src/component/ReviewForm/ReviewForm.tsx @@ -25,7 +25,7 @@ import { ReviewTags, tags, } from '@peterportal/types'; -import toaster from '../../hooks/toastifyHook'; +import spawnToast from '../../helpers/toastify'; interface ReviewFormProps extends ReviewProps { closeForm: () => void; @@ -63,13 +63,12 @@ const ReviewForm: FC = ({ const [userName, setUserName] = useState(reviewToEdit?.userDisplay ?? cookies.user?.name); const [validated, setValidated] = useState(false); const { darkMode } = useContext(ThemeContext); - const toastify = toaster(); useEffect(() => { if (show) { // form opened // if not logged in, close the form if (cookies.user === undefined) { - toastify('You must be logged in to add a review!'); + spawnToast('You must be logged in to add a review!', true); closeForm(); } @@ -87,7 +86,7 @@ const ReviewForm: FC = ({ setSubmitted(true); dispatch(editReview(res)); } catch (e) { - toastify((e as Error).message); + spawnToast((e as Error).message, true); } } else { try { @@ -95,7 +94,7 @@ const ReviewForm: FC = ({ setSubmitted(true); dispatch(addReview(res)); } catch (e) { - toastify((e as Error).message); + spawnToast((e as Error).message, true); } } }; @@ -116,7 +115,7 @@ const ReviewForm: FC = ({ } // check if CAPTCHA is completed for new reviews (captcha omitted for editing) if (!editing && !captchaToken) { - toastify('Please complete the CAPTCHA'); + spawnToast('Please complete the CAPTCHA', true); return; } const review = { @@ -155,7 +154,7 @@ const ReviewForm: FC = ({ newSelectedTags.push(tag); setSelectedTags(newSelectedTags); } else { - toastify('Cannot select more than 3 tags'); + spawnToast('Cannot select more than 3 tags', true); } } }; diff --git a/site/src/helpers/toastify.ts b/site/src/helpers/toastify.ts new file mode 100644 index 00000000..c0431a68 --- /dev/null +++ b/site/src/helpers/toastify.ts @@ -0,0 +1,17 @@ +import Toastify from 'toastify-js'; + +export default function spawnToast(text: string, error = false, style?: Record, callback?: () => void) { + return Toastify({ + text, + duration: 3000, + close: true, + gravity: 'bottom', + position: 'right', + style: { + background: error ? '#D22B2B' : 'var(--peterportal-primary-color-1)', + fontWeight: 'bold', + ...style, + }, + onClick: callback, + }).showToast(); +} diff --git a/site/src/hooks/toastifyHook.tsx b/site/src/hooks/toastifyHook.tsx deleted file mode 100644 index 69a2bc1a..00000000 --- a/site/src/hooks/toastifyHook.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import Toastify from 'toastify-js'; - -export default function toaster() { - return (text: string, style?: Record, callback?: () => void) => - Toastify({ - text, - duration: 3000, - close: true, - gravity: 'bottom', - position: 'right', - style: { background: 'var(--peterportal-primary-color-1)', fontWeight: 'bold', ...style }, - onClick: callback, - }).showToast(); -} diff --git a/site/src/pages/RoadmapPage/Planner.tsx b/site/src/pages/RoadmapPage/Planner.tsx index 79e255e4..31d8948a 100644 --- a/site/src/pages/RoadmapPage/Planner.tsx +++ b/site/src/pages/RoadmapPage/Planner.tsx @@ -22,7 +22,7 @@ import ImportTranscriptPopup from './ImportTranscriptPopup'; import { collapseAllPlanners, loadRoadmap, validatePlanner } from '../../helpers/planner'; import { Button, Modal } from 'react-bootstrap'; import trpc from '../../trpc'; -import toaster from '../../hooks/toastifyHook'; +import spawnToast from '../../helpers/toastify'; const Planner: FC = () => { const dispatch = useAppDispatch(); @@ -33,7 +33,6 @@ const Planner: FC = () => { const transfers = useAppSelector((state) => state.roadmap.transfers); const coursebag = useAppSelector((state) => state.roadmap.coursebag); const [showSyncModal, setShowSyncModal] = useState(false); - const toastify = toaster(); const [missingPrerequisites, setMissingPrerequisites] = useState(new Set()); const roadmapStr = JSON.stringify({ planners: collapseAllPlanners(allPlanData), @@ -72,13 +71,13 @@ const Planner: FC = () => { trpc.roadmaps.save .mutate(mongoRoadmap) .then(() => { - toastify(`Roadmap saved under ${cookies.user.email}`); + spawnToast(`Roadmap saved under ${cookies.user.email}`); }) .catch(() => { - toastify('Roadmap saved locally! Login to save it to your account.'); + spawnToast('Roadmap saved locally! Login to save it to your account.'); }); } else { - toastify('Roadmap saved locally! Login to save it to your account.'); + spawnToast('Roadmap saved locally! Login to save it to your account.', true); } }; diff --git a/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx b/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx index d3b1a2e5..f7b509a6 100644 --- a/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx +++ b/site/src/pages/RoadmapPage/RoadmapMultiplan.tsx @@ -14,7 +14,7 @@ import './RoadmapMultiplan.scss'; import * as Icon from 'react-bootstrap-icons'; import { Button } from 'semantic-ui-react'; import { Button as Button2, Form, Modal } from 'react-bootstrap'; -import toaster from '../../hooks/toastifyHook'; +import spawnToast from '../../helpers/toastify'; interface RoadmapSelectableItemProps { plan: RoadmapPlan; index: number; @@ -54,7 +54,6 @@ const RoadmapMultiplan: FC = () => { const [delIdx, setDelIdx] = useState(-1); const [newPlanName, setNewPlanName] = useState(allPlans.plans[allPlans.currentPlanIndex].name); const [showDropdown, setShowDropdown] = useState(false); - const toastify = toaster(); const isDuplicateName = () => allPlans.plans.find((p) => p.name === newPlanName); // name: name of the plan, content: stores the content of plan @@ -74,8 +73,8 @@ const RoadmapMultiplan: FC = () => { }; const handleSubmitNewPlan = () => { - if (!newPlanName) return toastify('Name cannot be empty'); - if (isDuplicateName()) return toastify('A plan with that name already exists'); + if (!newPlanName) return spawnToast('Name cannot be empty', true); + if (isDuplicateName()) return spawnToast('A plan with that name already exists', true); setIsOpen(false); addNewPlan(newPlanName); const newIndex = allPlans.plans.length; @@ -84,8 +83,8 @@ const RoadmapMultiplan: FC = () => { }; const modifyPlanName = () => { - if (!newPlanName) return toastify('Name cannot be empty'); - if (isDuplicateName()) return toastify('A plan with that name already exists'); + if (!newPlanName) return spawnToast('Name cannot be empty', true); + if (isDuplicateName()) return spawnToast('A plan with that name already exists', true); dispatch(setPlanName({ index: editIdx, name: newPlanName })); setEditIdx(-1); }; diff --git a/site/src/store/slices/roadmapSlice.ts b/site/src/store/slices/roadmapSlice.ts index 3fa1c331..1022963a 100644 --- a/site/src/store/slices/roadmapSlice.ts +++ b/site/src/store/slices/roadmapSlice.ts @@ -13,7 +13,7 @@ import { } from '../../types/types'; import type { RootState } from '../store'; import { TransferData } from '@peterportal/types'; -import toaster from '../../hooks/toastifyHook'; +import spawnToast from '../../helpers/toastify'; // Define a type for the slice state interface RoadmapPlanState { // Store planner data @@ -169,9 +169,8 @@ export const roadmapSlice = createSlice({ const newQuarter = action.payload.quarterData; // if year doesn't exist - const toastify = toaster(); if (!currentYears.includes(startYear)) { - toastify(`${startYear}-${startYear + 1} has not yet been added!`); + spawnToast(`${startYear}-${startYear + 1} has not yet been added!`, true); return; } @@ -182,7 +181,7 @@ export const roadmapSlice = createSlice({ // if duplicate quarter if (currentQuarters.includes(newQuarter.name)) { - toastify(`${quarterDisplayNames[newQuarter.name]} has already been added to Year ${yearIndex}!`); + spawnToast(`${quarterDisplayNames[newQuarter.name]} has already been added to Year ${yearIndex}!`, true); return; } @@ -219,16 +218,18 @@ export const roadmapSlice = createSlice({ const newYear = action.payload.yearData.startYear; const currentNames = state.plans[state.currentPlanIndex].content.yearPlans.map((e) => e.name); const newName = action.payload.yearData.name; - const toastify = toaster(); // if duplicate year if (currentYears.includes(newYear)) { - toastify(`${newYear}-${newYear + 1} has already been added as Year ${currentYears.indexOf(newYear) + 1}!`); + spawnToast( + `${newYear}-${newYear + 1} has already been added as Year ${currentYears.indexOf(newYear) + 1}!`, + true, + ); return; } // if duplicate name if (currentNames.includes(newName)) { const year = state.plans[state.currentPlanIndex].content.yearPlans[currentNames.indexOf(newName)].startYear; - toastify(`${newName} already exists from ${year} - ${year + 1}!`); + spawnToast(`${newName} already exists from ${year} - ${year + 1}!`, true); return; } @@ -247,10 +248,9 @@ export const roadmapSlice = createSlice({ const currentYears = state.plans[state.currentPlanIndex].content.yearPlans.map((e) => e.startYear); const newYear = action.payload.startYear; const yearIndex = action.payload.index; - const toastify = toaster(); // if duplicate year if (currentYears.includes(newYear)) { - toastify(`${newYear}-${newYear + 1} already exists as Year ${currentYears.indexOf(newYear) + 1}!`); + spawnToast(`${newYear}-${newYear + 1} already exists as Year ${currentYears.indexOf(newYear) + 1}!`, true); return; } @@ -274,11 +274,10 @@ export const roadmapSlice = createSlice({ const currentNames = state.plans[state.currentPlanIndex].content.yearPlans.map((e) => e.name); const newName = action.payload.name; const yearIndex = action.payload.index; - const toastify = toaster(); // if duplicate name if (currentNames.includes(newName)) { const year = state.plans[state.currentPlanIndex].content.yearPlans[yearIndex].startYear; - toastify(`${newName} already exists from ${year} - ${year + 1}!`); + spawnToast(`${newName} already exists from ${year} - ${year + 1}!`, true); return; }