diff --git a/app/src/components/contribute/ContributionFlow.tsx b/app/src/components/contribute/ContributionFlow.tsx index 4fbff290..af959258 100644 --- a/app/src/components/contribute/ContributionFlow.tsx +++ b/app/src/components/contribute/ContributionFlow.tsx @@ -59,6 +59,7 @@ const createVariantContData = (): VariantContribution => ({ const createObjectiveContData = (): ObjectiveContribution => ({ title: "", summary: "", + subjects: [], targets: [], featured: "", subObjectives: [], @@ -73,6 +74,32 @@ export default function ContributionFlow({ type, setType }: PropTypes) { const [objectiveContData, setObjectiveContData] = useState(createObjectiveContData); + useEffect(() => { + const savedGuide = sessionStorage.getItem("guide_cont_data"); + if (savedGuide) { + try { + setGuideContData(JSON.parse(savedGuide)); + } catch (e) {} + } + const savedObj = sessionStorage.getItem("objective_cont_data"); + if (savedObj) { + try { + setObjectiveContData(JSON.parse(savedObj)); + } catch (e) {} + } + }, []); + + useEffect(() => { + sessionStorage.setItem("guide_cont_data", JSON.stringify(guideContData)); + }, [guideContData]); + + useEffect(() => { + sessionStorage.setItem( + "objective_cont_data", + JSON.stringify(objectiveContData) + ); + }, [objectiveContData]); + const StepperInstance = useMemo(() => { if (!type) { return defineStepper(typeStep); @@ -128,6 +155,28 @@ function Inner({ objectiveContData: ObjectiveContribution; setObjectiveContData: Dispatch>; }) { + const hasHydratedStep = useRef(false); + + useEffect(() => { + if (stepper.current?.id) { + sessionStorage.setItem("cont_step", stepper.current.id); + } + }, [stepper.current?.id]); + + useEffect(() => { + if (hasHydratedStep.current) return; + if (type !== null) { + const savedStep = sessionStorage.getItem("cont_step"); + if (savedStep) { + requestAnimationFrame(() => { + try { + stepper.goTo(savedStep); + } catch (e) {} + }); + } + hasHydratedStep.current = true; + } + }, [type, stepper]); const pickType = (value: ContributionType) => { if (type !== value) { setGuideContData(createGuideContData()); @@ -388,6 +437,7 @@ function Inner({ /> diff --git a/app/src/components/contribute/StepperActionHeader.tsx b/app/src/components/contribute/StepperActionHeader.tsx index ff7fd118..91f4fa58 100644 --- a/app/src/components/contribute/StepperActionHeader.tsx +++ b/app/src/components/contribute/StepperActionHeader.tsx @@ -6,20 +6,26 @@ type PropTypes = { title: string; Stepper: any; nextDisabled?: boolean; + hideNext?: boolean; + onNextClick?: (e: React.MouseEvent) => void; hideBackBtn?: boolean; submitting?: boolean; onSaveDraft?: () => void; onPublish?: () => void; + publishText?: string; }; export const StepperActionHeader = ({ title, Stepper, nextDisabled, + hideNext, + onNextClick, submitting, hideBackBtn, onSaveDraft, onPublish, + publishText = "Submit for Review", }: PropTypes) => { return ( <> @@ -52,13 +58,17 @@ export const StepperActionHeader = ({ disabled={submitting} onClick={onPublish} > - Submit for Review + {publishText} - ) : ( - + ) : !hideNext ? ( + Next - )} + ) : null} diff --git a/app/src/components/contribute/steps/ObjectiveDetails.tsx b/app/src/components/contribute/steps/ObjectiveDetails.tsx index 946a55c0..07ec8d4b 100644 --- a/app/src/components/contribute/steps/ObjectiveDetails.tsx +++ b/app/src/components/contribute/steps/ObjectiveDetails.tsx @@ -1,4 +1,5 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; +import { toast } from "sonner"; import type { Dispatch, SetStateAction } from "react"; import type { ObjectiveContribution } from "@/types/contributions"; @@ -14,6 +15,7 @@ import { Textarea } from "@/components/ui/textarea"; import { Combobox } from "@/components/ui/combobox"; import guidesData from "@/data/guides.json"; +import subjectsData from "@/data/subjects.json"; type PropTypes = { Stepper: any; @@ -26,6 +28,7 @@ export const ObjectiveDetails = ({ objectiveContData, setObjectiveContData, }: PropTypes) => { + const [showErrors, setShowErrors] = useState(false); const guides = guidesData.map((g) => { return { value: g.slug, @@ -44,22 +47,32 @@ export const ObjectiveDetails = ({ return ( objectiveContData.title.trim() === "" || objectiveContData.summary.trim() === "" || + objectiveContData.subjects.length === 0 || objectiveContData.targets.length === 0 || !objectiveContData.featured ); }, [ objectiveContData.title, objectiveContData.summary, + objectiveContData.subjects, objectiveContData.targets, objectiveContData.featured, ]); + const handleNextClick = (e: React.MouseEvent) => { + if (isNextDisabled) { + e.preventDefault(); + setShowErrors(true); + toast.error("Please fill out all required fields."); + } + }; + return ( @@ -79,7 +92,11 @@ export const ObjectiveDetails = ({ autoComplete="Title" maxLength={50} placeholder="Choose a title. (Maximum 50 characters)." - className="h-10 rounded-md" + className={`h-10 rounded-md ${ + showErrors && !objectiveContData.title.trim() + ? "border-destructive focus-visible:ring-destructive/20" + : "" + }`} required value={objectiveContData.title} onChange={(e) => @@ -103,7 +120,11 @@ export const ObjectiveDetails = ({