diff --git a/form-builder/backend/database.js b/form-builder/backend/database.js index 3cc0910..4889a0f 100644 --- a/form-builder/backend/database.js +++ b/form-builder/backend/database.js @@ -23,9 +23,29 @@ let forms = [ } ]; +let submissions = []; + +function addSubmission(formId, data) { + submissions.push({ formId, data, submittedAt: new Date().toISOString() }); +} + +function getSubmissions(formId) { + return submissions.filter(s => String(s.formId) === String(formId)); +} + +function getSubmissionCount(formId) { + return getSubmissions(formId).length; +} + function getForms() { - // Return only form metadata (not full config) - return forms.map(({ id, name, description, createdAt }) => ({ id, name, description, createdAt })); + // Return only form metadata (not full config), plus filledCount + return forms.map(({ id, name, description, createdAt }) => ({ + id, + name, + description, + createdAt, + filledCount: getSubmissionCount(id) + })); } function getFormById(id) { @@ -59,5 +79,8 @@ module.exports = { getFormById, addForm, updateFormById, - deleteFormById + deleteFormById, + addSubmission, + getSubmissions, + getSubmissionCount }; diff --git a/form-builder/backend/routes/formRoutes.js b/form-builder/backend/routes/formRoutes.js index f4733ae..d64d492 100644 --- a/form-builder/backend/routes/formRoutes.js +++ b/form-builder/backend/routes/formRoutes.js @@ -1,6 +1,6 @@ const express = require('express'); const router = express.Router(); -const { getForms, getFormById, addForm, updateFormById } = require('../database'); +const { getForms, getFormById, addForm, updateFormById, deleteFormById, addSubmission, getSubmissions } = require('../database'); const Form = require('../models/form'); // Get all forms (metadata only) @@ -40,4 +40,20 @@ router.delete('/forms/:id', (req, res) => { res.json({ success: true }); }); +// Submit a filled form +router.post('/forms/:id/submit', (req, res) => { + const form = getFormById(req.params.id); + if (!form) return res.status(404).json({ error: 'Form not found' }); + addSubmission(req.params.id, req.body); + res.status(201).json({ success: true }); +}); + +// Get all submissions for a form +router.get('/forms/:id/submissions', (req, res) => { + const form = getFormById(req.params.id); + if (!form) return res.status(404).json({ error: 'Form not found' }); + const submissions = getSubmissions(req.params.id); + res.json(submissions); +}); + module.exports = router; diff --git a/form-builder/src/App.jsx b/form-builder/src/App.jsx index ef54e45..f2f1fd3 100644 --- a/form-builder/src/App.jsx +++ b/form-builder/src/App.jsx @@ -6,6 +6,7 @@ const App = () => { } /> + } /> } /> } /> } /> diff --git a/form-builder/src/FormBuilder.jsx b/form-builder/src/FormBuilder.jsx index 46c0bb7..65beee4 100644 --- a/form-builder/src/FormBuilder.jsx +++ b/form-builder/src/FormBuilder.jsx @@ -5,7 +5,7 @@ import AddForm from './components/AddForm'; import FormBuilderView from './components/FormBuilderView'; import { Share, Edit3 } from 'lucide-react'; import { Button } from './ui'; -import { getForms, getFormById, createForm, updateForm, deleteForm } from './api'; +import { getForms, getFormById, createForm, updateForm, deleteForm, submitForm, getFormSubmissions } from './api'; const FormBuilder = () => { const { id } = useParams(); @@ -19,6 +19,10 @@ const FormBuilder = () => { }); const [editingFormId, setEditingFormId] = useState(null); const [viewingForm, setViewingForm] = useState(null); + const [userFormData, setUserFormData] = useState({}); + const [submitSuccess, setSubmitSuccess] = useState(false); + const [submissions, setSubmissions] = useState([]); + const [userFormErrors, setUserFormErrors] = useState({}); const fieldTypes = [ { type: 'text', label: 'Text Input', icon: '📝' }, @@ -85,26 +89,37 @@ const FormBuilder = () => { // eslint-disable-next-line }, [location.pathname, id]); + // Fetch submissions for view route + useEffect(() => { + if (id && location.pathname.endsWith('/view')) { + getFormSubmissions(id).then(setSubmissions).catch(() => setSubmissions([])); + } + }, [id, location.pathname]); + // Handlers const handleCreateForm = () => navigate('/add'); const handleViewForm = (id) => navigate(`/form/${id}/view`); const handleNextToBuilder = async () => { + let draftForm = { ...currentForm }; if (currentForm.template && currentForm.template !== 'blank') { const templateMeta = forms.find(f => f.name.toLowerCase().includes(currentForm.template)); if (templateMeta) { try { const templateForm = await getFormById(templateMeta.id); - setCurrentForm(prev => ({ ...prev, fields: [...templateForm.fields] })); - } catch { - alert('Failed to load template form'); - console.error('Failed to load template form:', templateMeta.id); - return; - } + draftForm.fields = [...templateForm.fields]; + } catch {} } } - navigate('/form/new/edit'); + // Create draft form in backend + try { + const created = await createForm(draftForm); + await loadForms(); + navigate(`/form/${created.id}/edit`); + } catch { + alert('Failed to create form'); + } }; const handleAddField = (type) => { @@ -174,7 +189,6 @@ const FormBuilder = () => { }; const renderFieldPreview = (field) => { - validateForm(); const baseProps = { placeholder: field.placeholder, className: 'w-full' }; switch (field.type) { case 'text': @@ -263,99 +277,504 @@ const FormBuilder = () => { return errors; }; + // Handler for user form submit + const handleUserFormSubmit = async (e) => { + e.preventDefault(); + // Validate required fields + const errors = {}; + if (viewingForm && viewingForm.fields) { + viewingForm.fields.forEach(field => { + if (field.required && (!userFormData[field.id] || userFormData[field.id].toString().trim() === '')) { + errors[field.id] = 'This field is required'; + } + }); + } + setUserFormErrors(errors); + if (Object.keys(errors).length > 0) return; + try { + await submitForm(id, userFormData); + setSubmitSuccess(true); + setUserFormData({}); + setUserFormErrors({}); + } catch { + alert('Failed to submit form'); + } + }; + const handleEditForm = (id) => { + navigate(`/form/${id}/edit`); + }; // Render logic based on route if (location.pathname === '/' || location.pathname === '/dashboard') { - return ; + return ( +
+
+
+
+
+
+ 📋 +
+
+

+ Form Builder +

+

Create and manage your forms

+
+
+ +
+
+
+
+ +
+
+ ); } + + // Add Form Route if (location.pathname === '/add') { - return navigate('/')} handleNextToBuilder={handleNextToBuilder} />; + return ( +
+
+
+
+
+ +
+

+ Create New Form +

+

Start building your form

+
+
+
+
+
+
+
+ navigate('/')} + handleNextToBuilder={handleNextToBuilder} + /> +
+
+
+ ); } + + // Form Edit Route if (id && location.pathname.endsWith('/edit') && currentForm) { return ( - navigate('/')} - handleAddField={handleAddField} - handleDragEnd={handleDragEnd} - selectedField={selectedField} - handleFieldSelect={handleFieldSelect} - renderFieldPreview={renderFieldPreview} - fieldConfig={fieldConfig} - setFieldConfig={setFieldConfig} - handleFieldUpdate={handleFieldUpdate} - handleSaveForm={handleSaveForm} - setCurrentForm={setCurrentForm} - setSelectedField={setSelectedField} - /> +
+
+
+
+
+ +
+

+ Edit Form +

+

{currentForm.name || 'Untitled Form'}

+
+
+ +
+
+
+
+
+ navigate('/')} + handleAddField={handleAddField} + handleDragEnd={handleDragEnd} + selectedField={selectedField} + handleFieldSelect={handleFieldSelect} + renderFieldPreview={renderFieldPreview} + fieldConfig={fieldConfig} + setFieldConfig={setFieldConfig} + handleFieldUpdate={handleFieldUpdate} + handleSaveForm={handleSaveForm} + setCurrentForm={setCurrentForm} + setSelectedField={setSelectedField} + /> +
+
+
); } + + // Form View Route if (id && location.pathname.endsWith('/view') && viewingForm) { return ( -
-
-
-
-
-

{viewingForm.name}

-

{viewingForm.description}

+
+
+
+
+
+ +
+

+ {viewingForm.name} +

+

{viewingForm.description}

+
+
+
+ +
-
-
- {viewingForm.fields && viewingForm.fields.length > 0 ? ( - viewingForm.fields.map((field, idx) => ( -
- - {/* Render field preview (read-only) */} - {renderFieldPreview(field)} - {field.helperText && ( -

{field.helperText}

- )} +
+
+ +
+
+ {/* Form Preview */} +
+
+

Form Preview

+
+
+
+ {viewingForm.fields && viewingForm.fields.length > 0 ? ( + viewingForm.fields.map((field, idx) => ( +
+ +
+ {renderFieldPreview(field)} +
+
+ {field.helperText && ( +

{field.helperText}

+ )} +
+ )) + ) : ( +
+
📝
+

No fields in this form yet.

- )) - ) : ( -
No fields in this form.
- )} + )} +
+
+ + {/* Submissions */} +
+
+

+ Submissions ({submissions.length}) +

+
+
+
+ {submissions.length === 0 ? ( +
+
📊
+

No submissions yet.

+

Share your form to start collecting responses!

+
+ ) : ( + submissions.map((sub, idx) => ( +
+
+
+ {new Date(sub.submittedAt).toLocaleString()} +
+
+
+
+ {Object.entries(sub.data).map(([fid, val]) => ( +
+
+
+ + {viewingForm.fields.find(f => String(f.id) === String(fid))?.label || fid}: + + {val} +
+
+ ))} +
+
+ )) + )} +
); } + + // Public Preview Route if (id && location.pathname.endsWith('/public/preview') && viewingForm) { + if (submitSuccess) { + return ( +
+
+
+
+

+ Thank you! +

+

Your form has been submitted successfully.

+
+
+
+
+ ); + } + return ( -
-
-
-
-

{viewingForm.name}

-

{viewingForm.description}

+
+
+
+
+
+
+ 📋 +
+
+

+ {viewingForm.name} +

+

{viewingForm.description}

+
+
+
-
- {viewingForm.fields && viewingForm.fields.length > 0 ? ( - viewingForm.fields.map((field, idx) => ( -
-
+
+ +
+
+
+ {viewingForm.fields.map((field, idx) => ( +
+ + + {field.type === 'text' && ( + setUserFormData(prev => ({ ...prev, [field.id]: e.target.value }))} + placeholder={field.placeholder} + required={field.required} + /> + )} + + {field.type === 'textarea' && ( +