From ce55be26094a1f1ce5d93b7c955557ff56d41b81 Mon Sep 17 00:00:00 2001 From: Neeraj-Kondaveeti Date: Tue, 18 Nov 2025 13:28:19 -0500 Subject: [PATCH 1/4] Fix: Added unsaved changes for refresh and navigation events --- .../Collaboration/JobFormbuilder.jsx | 427 ++++++++++-------- 1 file changed, 229 insertions(+), 198 deletions(-) diff --git a/src/components/Collaboration/JobFormbuilder.jsx b/src/components/Collaboration/JobFormbuilder.jsx index f7180d8bd4..a632282e3f 100644 --- a/src/components/Collaboration/JobFormbuilder.jsx +++ b/src/components/Collaboration/JobFormbuilder.jsx @@ -1,10 +1,9 @@ /* eslint-disable no-alert */ /* eslint-disable no-console */ -// eslint-disable-next-line no-alert import { useState, useEffect } from 'react'; import axios from 'axios'; import { useSelector } from 'react-redux'; -import { v4 as uuidv4 } from 'uuid'; +import { Prompt } from 'react-router-dom'; // for navigation blocking import styles from './JobFormBuilder.module.css'; import { ENDPOINTS } from '~/utils/URL'; import OneCommunityImage from './One-Community-Horizontal-Homepage-Header-980x140px-2.png'; @@ -14,19 +13,41 @@ import QuestionEditModal from './QuestionEditModal'; function JobFormBuilder() { const { role } = useSelector(state => state.auth.user); - const [formFields, setFormFields] = useState([]); const darkMode = useSelector(state => state.theme.darkMode); + + // ---------------------------------- + // FORM STATE + // ---------------------------------- + const [formFields, setFormFields] = useState([]); + const [initialFormFields, setInitialFormFields] = useState([]); + + const [jobTitle, setJobTitle] = useState('Please Choose an option'); + const [initialJobTitle, setInitialJobTitle] = useState('Please Choose an option'); + + const [currentFormId, setCurrentFormId] = useState(null); + const [newField, setNewField] = useState({ questionText: '', questionType: 'textbox', - options: [], // For checkbox, radio, and dropdown input types + options: [], visible: true, }); - // Dynamic Form ID Management - const [currentFormId, setCurrentFormId] = useState(null); + const initialNewField = { + questionText: '', + questionType: 'textbox', + options: [], + visible: true, + }; + + const [newOption, setNewOption] = useState(''); + + const [editModalOpen, setEditModalOpen] = useState(false); + const [editingQuestion, setEditingQuestion] = useState(null); + const [editingIndex, setEditingIndex] = useState(null); + + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); - const [jobTitle, setJobTitle] = useState('Please Choose an option'); const jobPositions = [ 'APPLIED THROUGH SITE - SEEKING SOFTWARE POSITION', 'APPLIED THROUGH SITE - GENERAL', @@ -70,173 +91,177 @@ function JobFormBuilder() { 'DATA ANALYST APPLICATION', ]; - const [newOption, setNewOption] = useState(''); - const [editModalOpen, setEditModalOpen] = useState(false); - const [editingQuestion, setEditingQuestion] = useState(null); - const [editingIndex, setEditingIndex] = useState(null); + // ---------------------------------- + // BLOCK PAGE REFRESH + // ---------------------------------- + useEffect(() => { + const handler = e => { + if (hasUnsavedChanges) { + e.preventDefault(); + e.returnValue = ''; + } + }; + window.addEventListener('beforeunload', handler); + return () => window.removeEventListener('beforeunload', handler); + }, [hasUnsavedChanges]); - // Auto-load existing form on component mount useEffect(() => { - const loadFirstAvailableForm = async () => { + const loadForm = async () => { try { const response = await axios.get(ENDPOINTS.GET_ALL_JOB_FORMS); - if (response.data && response.data.length > 0) { - const firstForm = response.data[0]; - const formId = firstForm._id || firstForm.id; + if (response.data?.length > 0) { + const form = response.data[0]; + const id = form._id || form.id; - setCurrentFormId(formId); - setFormFields(firstForm.questions || []); - setJobTitle(firstForm.title || 'Please Choose an option'); + setCurrentFormId(id); + setFormFields(form.questions || []); + setInitialFormFields(form.questions || []); - console.log('Auto-loaded form:', formId); + const loadedTitle = form.title || 'Please Choose an option'; + setJobTitle(loadedTitle); + setInitialJobTitle(loadedTitle); + + setNewField(initialNewField); + setHasUnsavedChanges(false); } - } catch (error) { - console.error('Error auto-loading form:', error); + } catch (err) { + console.error(err); } }; - - loadFirstAvailableForm(); + loadForm(); }, []); - // const ensureFormExists = async () => { - // if (!currentFormId) { - // console.warn('No form ID available for this operation'); - // return false; - // } - // return true; - // }; + const handleTemplateManagerEvents = e => { + const el = e.target; - // CRUD Functions with Dynamic Form ID - const cloneField = async (field, index) => { - const clonedField = JSON.parse(JSON.stringify(field)); + // Template Name input + if (el.placeholder === 'Template Name') { + if (el.value.trim() !== '') setHasUnsavedChanges(true); + } + + // Template dropdown + if (el.tagName === 'SELECT' && el.closest('#template-manager-wrapper')) { + setHasUnsavedChanges(true); + } + + // Append Template button + if (el.textContent === 'Append Template') { + setHasUnsavedChanges(true); + } + }; + + useEffect(() => { + const changed = + JSON.stringify(formFields) !== JSON.stringify(initialFormFields) || + jobTitle !== initialJobTitle || + JSON.stringify(newField) !== JSON.stringify(initialNewField); - // Update local state immediately - const newFields = [ - ...formFields.slice(0, index + 1), - clonedField, - ...formFields.slice(index + 1), - ]; - setFormFields(newFields); + setHasUnsavedChanges(changed); + }, [formFields, jobTitle, newField, initialFormFields, initialJobTitle]); + + // ---------------------------------- + // FIELD OPERATIONS + // ---------------------------------- + const cloneField = async (field, index) => { + const clone = JSON.parse(JSON.stringify(field)); + const updated = [...formFields.slice(0, index + 1), clone, ...formFields.slice(index + 1)]; + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { await axios.post(ENDPOINTS.ADD_QUESTION(currentFormId), { - question: clonedField, + question: clone, position: index + 1, }); - } catch (error) { - console.error('Error cloning question on server:', error); + } catch (err) { + console.error(err); } } }; const moveField = async (index, direction) => { const newIndex = direction === 'up' ? index - 1 : index + 1; + if (newIndex < 0 || newIndex >= formFields.length) return; - if ( - (direction === 'up' && index > 0) || - (direction === 'down' && index < formFields.length - 1) - ) { - // Update local state immediately - const newFields = [...formFields]; - [newFields[index], newFields[newIndex]] = [newFields[newIndex], newFields[index]]; - setFormFields(newFields); - - // Sync with backend if form exists - if (currentFormId) { - try { - await axios.put(ENDPOINTS.REORDER_QUESTIONS(currentFormId), { - fromIndex: index, - toIndex: newIndex, - }); - } catch (error) { - console.error('Error reordering questions on server:', error); - } + const updated = [...formFields]; + [updated[index], updated[newIndex]] = [updated[newIndex], updated[index]]; + setFormFields(updated); + + if (currentFormId) { + try { + await axios.put(ENDPOINTS.REORDER_QUESTIONS(currentFormId), { + fromIndex: index, + toIndex: newIndex, + }); + } catch (err) { + console.error(err); } } }; const deleteField = async index => { - // Update local state immediately - const newFields = [...formFields]; - newFields.splice(index, 1); - setFormFields(newFields); + const updated = [...formFields]; + updated.splice(index, 1); + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { await axios.delete(ENDPOINTS.DELETE_QUESTION(currentFormId, index)); - console.log('Question deleted successfully'); - } catch (error) { - console.error('Error deleting question on server:', error); + } catch (err) { + console.error(err); } } }; const editField = (field, index) => { - // Transform the field structure to match what QuestionEditModal expects - const questionForEdit = { + setEditingQuestion({ label: field.questionText, type: field.questionType, options: field.options, required: field.required || false, placeholder: field.placeholder || '', - }; + }); - setEditingQuestion(questionForEdit); setEditingIndex(index); setEditModalOpen(true); }; - const handleSaveEditedQuestion = async editedQuestion => { - const updatedField = { - ...formFields[editingIndex], - questionText: editedQuestion.label, - questionType: editedQuestion.type, - options: editedQuestion.options || [], - required: editedQuestion.required, - placeholder: editedQuestion.placeholder, + const handleSaveEditedQuestion = async edited => { + const updated = [...formFields]; + updated[editingIndex] = { + ...updated[editingIndex], + questionText: edited.label, + questionType: edited.type, + options: edited.options || [], + required: edited.required, + placeholder: edited.placeholder, }; - // Update local state immediately - const updatedFields = [...formFields]; - updatedFields[editingIndex] = updatedField; - setFormFields(updatedFields); + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { - await axios.put(ENDPOINTS.UPDATE_QUESTION(currentFormId, editingIndex), updatedField); - console.log('Question updated successfully'); - } catch (error) { - console.error('Error updating question on server:', error); + await axios.put( + ENDPOINTS.UPDATE_QUESTION(currentFormId, editingIndex), + updated[editingIndex], + ); + } catch (err) { + console.error(err); } } - // Close the modal setEditModalOpen(false); setEditingQuestion(null); setEditingIndex(null); }; - const handleCancelEdit = () => { - setEditModalOpen(false); - setEditingQuestion(null); - setEditingIndex(null); - }; - - // Import questions from template - const importQuestions = questions => { - setFormFields(questions); - }; - const handleAddOption = () => { - if (newOption.trim() === '') { - alert('Option cannot be empty!'); + if (!newOption.trim()) { + alert('Option cannot be empty'); return; } + setNewField(prev => ({ ...prev, options: [...prev.options, newOption], @@ -245,8 +270,8 @@ function JobFormBuilder() { }; const handleAddField = async () => { - if (newField.questionText.trim() === '') { - alert('Field label is required!'); + if (!newField.questionText.trim()) { + alert('Field label is required'); return; } @@ -254,69 +279,55 @@ function JobFormBuilder() { ['checkbox', 'radio', 'dropdown'].includes(newField.questionType) && newField.options.length === 0 ) { - alert('You must add at least one option for this field!'); + alert('Please add at least one option'); return; } - // Update local state immediately - const updatedFields = [...formFields, newField]; - setFormFields(updatedFields); + const updated = [...formFields, newField]; + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { await axios.post(ENDPOINTS.ADD_QUESTION(currentFormId), { question: newField, position: formFields.length, }); - } catch (error) { - console.error('Error adding question to server:', error); + } catch (err) { + console.error(err); } } - setNewField({ questionText: '', questionType: 'textbox', options: [], visible: true }); + setNewField(initialNewField); }; - const changeVisiblity = (event, field) => { - const updatedFields = formFields.map( - item => - item.questionText === field.questionText && item.questionType === field.questionType - ? { ...item, visible: event.target.checked } // Return the updated item - : item, // Return the unchanged item + const changeVisibility = (e, field) => { + const updated = formFields.map(item => + item.questionText === field.questionText && item.questionType === field.questionType + ? { ...item, visible: e.target.checked } + : item, ); - setFormFields(updatedFields); - }; - - const handleSubmit = async e => { - e.preventDefault(); - - const formIdToUse = currentFormId || '6753982566fcf3275f129eb4'; - - try { - await axios.put(ENDPOINTS.UPDATE_JOB_FORM, { - formId: formIdToUse, - title: jobTitle, - questions: formFields, - description: '', - }); - console.log('Form updated successfully'); - alert('Form saved successfully!'); - } catch (error) { - console.error('Error updating form:', error); - alert('Failed to save form. Please try again.'); - } + setFormFields(updated); }; + // ---------------------------------- + // RENDER + // ---------------------------------- return (
+ {/* BLOCK INTERNAL NAVIGATION */} + +
One Community Logo +
@@ -324,89 +335,99 @@ function JobFormBuilder() { Go
+
- {console.log(role)} +

FORM CREATION

- {role === 'Owner' || role === 'Administrator' ? ( + + {(role === 'Owner' || role === 'Administrator') && (

Fill the form with questions about a specific position you want to create an ad for. The default questions will automatically appear and are alredy selected. You can pick and choose them with the checkbox.

- + + {/* TEMPLATE MANAGER WRAPPER FOR EVENT DELEGATION */} + {/* eslint-disable jsx-a11y/no-static-element-interactions */} + {/* eslint-disable jsx-a11y/click-events-have-key-events */} + +
{formFields.map((field, index) => ( -
+
changeVisiblity(event, field)} + onVisibilityChange={e => changeVisibility(e, field)} /> -
+ +
+
{field.questionType === 'textbox' && ( - + )} + {field.questionType === 'date' && ( - + )} + {field.questionType === 'textarea' && (