From 67264c197a55ccfa21f36d6f64cc7b5ab67884e4 Mon Sep 17 00:00:00 2001 From: abk-tech Date: Mon, 22 Jun 2026 21:45:58 +0100 Subject: [PATCH 1/3] implement-Basic info form --- .../campaigns/steps/BasicInfoStep.jsx | 163 ++++++++++++++++++ src/pages/campaigns/CreateCampaignPage.jsx | 20 ++- 2 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 src/components/campaigns/steps/BasicInfoStep.jsx diff --git a/src/components/campaigns/steps/BasicInfoStep.jsx b/src/components/campaigns/steps/BasicInfoStep.jsx new file mode 100644 index 0000000..5301956 --- /dev/null +++ b/src/components/campaigns/steps/BasicInfoStep.jsx @@ -0,0 +1,163 @@ +import { useEffect, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; +import { selectDraftCampaign, updateDraftCampaign } from '../../../../features/campaigns/campaignsSlice'; + +const CATEGORIES = ['Education', 'Health', 'Environment', 'Disaster Relief', 'Community']; + +const schema = yup.object({ + title: yup.string().required('Title is required').max(100, 'Must be 100 characters or less'), + category: yup.string().required('Category is required'), + shortDescription: yup.string().required('Short description is required').max(200, 'Must be 200 characters or less'), + fullStory: yup.string().required('Full story is required'), +}); + +const BasicInfoStep = ({ validationRef }) => { + const dispatch = useDispatch(); + const draft = useSelector(selectDraftCampaign); + + const { register, handleSubmit, watch, trigger, formState, setValue } = useForm({ + resolver: yupResolver(schema), + defaultValues: { + title: draft.title || '', + category: draft.category || '', + shortDescription: draft.description || '', + fullStory: draft.fullStory || draft.description || '', + }, + mode: 'onChange', + }); + + // Keep local form values in sync if draft changes externally + useEffect(() => { + setValue('title', draft.title || ''); + setValue('category', draft.category || ''); + setValue('shortDescription', draft.description || ''); + setValue('fullStory', draft.fullStory || draft.description || ''); + }, [draft, setValue]); + + // Debounced autosave to Redux when form values change + const timeoutRef = useRef(null); + const watched = watch(); + useEffect(() => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + dispatch( + updateDraftCampaign({ + title: watched.title || '', + category: watched.category || '', + description: watched.shortDescription || '', + fullStory: watched.fullStory || '', + }) + ); + }, 550); + return () => clearTimeout(timeoutRef.current); + }, [watched, dispatch]); + + // Expose a validate function to parent via validationRef + useEffect(() => { + if (!validationRef) return; + validationRef.current = { + validate: async () => { + const valid = await trigger(); + if (valid) { + // ensure final save before proceeding + const values = await handleSubmit((vals) => vals)(); + dispatch( + updateDraftCampaign({ + title: values.title, + category: values.category, + description: values.shortDescription, + fullStory: values.fullStory, + }) + ); + } + return valid; + }, + }; + return () => { + if (validationRef.current && validationRef.current.validate) { + validationRef.current = null; + } + }; + }, [validationRef, trigger, handleSubmit, dispatch]); + + const title = watch('title') || ''; + const shortDescription = watch('shortDescription') || ''; + + return ( +
+
+ + +
+ {formState.errors.title ? formState.errors.title.message : ''} + {title.length}/100 +
+
+ +
+ + +
{formState.errors.category?.message || ''}
+
+ +
+ +