-
Notifications
You must be signed in to change notification settings - Fork 265
Feature/dynamic google form #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,107 @@ | ||
| import "./App.css"; | ||
| import { Routes, Route, HashRouter } from "react-router-dom"; | ||
| import Home from "./pages/Home"; | ||
| import Question_Type from "./pages/Question_Type"; | ||
| import Text_Input from "./pages/Text_Input"; | ||
| import Output from "./pages/Output"; | ||
| import Previous from "./pages/Previous"; | ||
| import NotFound from "./pages/PageNotFound"; | ||
|
|
||
| function App() { | ||
| import React, { useEffect, useState } from "react"; | ||
|
|
||
| function DynamicForm() { | ||
| const [questions, setQuestions] = useState([]); | ||
| const [responses, setResponses] = useState({}); | ||
|
|
||
| // Fetch form questions from Google Apps Script | ||
| useEffect(() => { | ||
| fetch( | ||
| "https://script.google.com/macros/s/AKfycbxRamNfuS1b9yBXapdCs9gB3ScRhpxiRPbjTO0qrzeYel0BJEq4VyTGX3GnZ_-KIsZXkw/exec" | ||
| ) | ||
| .then((res) => res.json()) | ||
| .then((data) => setQuestions(data)) | ||
| .catch((err) => console.error("Error fetching form:", err)); | ||
| }, []); | ||
|
|
||
| const handleChange = (id, value) => { | ||
| setResponses((prev) => ({ ...prev, [id]: value })); | ||
| }; | ||
|
|
||
| const handleSubmit = (e) => { | ||
| e.preventDefault(); | ||
| console.log("User Responses:", responses); | ||
| alert("Form submitted! Check console for responses."); | ||
| }; | ||
|
|
||
| return ( | ||
| <HashRouter> | ||
| <Routes> | ||
| <Route path="/" element={<Home />} /> | ||
| <Route path="/question-type" element={<Question_Type />} /> | ||
| <Route path="/input" element={<Text_Input />} /> | ||
| <Route path="/output" element={<Output />} /> | ||
| <Route path="/history" element={<Previous />} /> | ||
| <Route path="*" element={<NotFound />} /> | ||
| </Routes> | ||
| </HashRouter> | ||
| <div style={{ padding: "20px" }}> | ||
| <h2>Dynamic Google Form</h2> | ||
| <form onSubmit={handleSubmit}> | ||
| {questions.map((q) => ( | ||
| <div key={q.id} style={{ marginBottom: "20px" }}> | ||
| <label> | ||
| <strong>{q.title}</strong> | ||
| </label> | ||
| <br /> | ||
|
|
||
| {q.type === "TEXT" && ( | ||
| <input | ||
| type="text" | ||
| onChange={(e) => handleChange(q.id, e.target.value)} | ||
| /> | ||
| )} | ||
|
|
||
| {q.type === "PARAGRAPH_TEXT" && ( | ||
| <textarea | ||
| rows="4" | ||
| cols="40" | ||
| onChange={(e) => handleChange(q.id, e.target.value)} | ||
| /> | ||
| )} | ||
|
|
||
| {q.type === "CHECKBOX" && | ||
| q.choices?.map((choice, i) => ( | ||
| <label key={i} style={{ display: "block" }}> | ||
| <input | ||
| type="checkbox" | ||
| value={choice} | ||
| onChange={(e) => { | ||
| const prev = responses[q.id] || []; | ||
| if (e.target.checked) { | ||
| handleChange(q.id, [...prev, choice]); | ||
| } else { | ||
| handleChange( | ||
| q.id, | ||
| prev.filter((c) => c !== choice) | ||
| ); | ||
| } | ||
| }} | ||
| /> | ||
| {choice} | ||
| </label> | ||
| ))} | ||
|
|
||
| {q.type === "LIST" && ( | ||
| <select onChange={(e) => handleChange(q.id, e.target.value)}> | ||
| <option value="">Select</option> | ||
| {q.choices?.map((choice, i) => ( | ||
| <option key={i} value={choice}> | ||
| {choice} | ||
| </option> | ||
| ))} | ||
| </select> | ||
| )} | ||
|
|
||
| {q.type === "MULTIPLE_CHOICE" && | ||
| q.choices?.map((choice, i) => ( | ||
| <label key={i} style={{ display: "block" }}> | ||
| <input | ||
| type="radio" | ||
| name={q.id} | ||
| value={choice} | ||
| onChange={(e) => handleChange(q.id, e.target.value)} | ||
| /> | ||
| {choice} | ||
| </label> | ||
| ))} | ||
| </div> | ||
| ))} | ||
|
|
||
| <button type="submit">Submit</button> | ||
| </form> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default App; | ||
| export default DynamicForm; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,113 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React, { useEffect, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function DynamicForm() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [questions, setQuestions] = useState([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [answers, setAnswers] = useState({}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 👉 Replace this with your deployed Google Apps Script URL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const GOOGLE_SCRIPT_URL = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://script.google.com/macros/s/AKfycbxRamNfuS1b9yBXapdCs9gB3ScRhpxiRPbjTO0qrzeYel0BJEq4VyTGX3GnZ_-KIsZXkw/exec"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move the Google Apps Script URL to environment variables. Hardcoding the script URL exposes the endpoint publicly and makes it difficult to maintain across environments (dev, staging, prod). Move to an environment variable: - // 👉 Replace this with your deployed Google Apps Script URL
- const GOOGLE_SCRIPT_URL =
- "https://script.google.com/macros/s/AKfycbxRamNfuS1b9yBXapdCs9gB3ScRhpxiRPbjTO0qrzeYel0BJEq4VyTGX3GnZ_-KIsZXkw/exec";
+ const GOOGLE_SCRIPT_URL = process.env.REACT_APP_GOOGLE_SCRIPT_URL;Then add to 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fetch questions from Google Script | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch(GOOGLE_SCRIPT_URL) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((res) => res.json()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((data) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Fetched form data:", data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setQuestions(data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .catch((err) => console.error("Error fetching form data:", err)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add loading state and improve error handling. The component doesn't indicate when data is being fetched, and errors are only logged without user feedback. Apply this diff: function DynamicForm() {
const [questions, setQuestions] = useState([]);
const [answers, setAnswers] = useState({});
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
const GOOGLE_SCRIPT_URL =
"https://script.google.com/macros/s/AKfycbxRamNfuS1b9yBXapdCs9gB3ScRhpxiRPbjTO0qrzeYel0BJEq4VyTGX3GnZ_-KIsZXkw/exec";
useEffect(() => {
+ setLoading(true);
fetch(GOOGLE_SCRIPT_URL)
.then((res) => res.json())
.then((data) => {
console.log("Fetched form data:", data);
setQuestions(data);
+ setLoading(false);
})
- .catch((err) => console.error("Error fetching form data:", err));
+ .catch((err) => {
+ console.error("Error fetching form data:", err);
+ setError("Failed to load form. Please try again later.");
+ setLoading(false);
+ });
}, []);Then update the render logic to show loading/error states: if (loading) return <div>Loading form...</div>;
if (error) return <div style={{ color: "red" }}>{error}</div>;🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle input change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleChange = (id, value) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setAnswers((prev) => ({ ...prev, [id]: value })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle form submission | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleSubmit = (e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Submitted answers:", answers); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert("Form submitted! Check console for answers."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implement actual form submission and validation. The form currently only logs responses locally without submitting them to the Google Apps Script endpoint or validating required fields. Consider implementing:
Example: const handleSubmit = (e) => {
e.preventDefault();
+
+ // Validate required fields
+ const requiredQuestions = questions.filter(q => q.required);
+ const missingAnswers = requiredQuestions.filter(q => !answers[q.id]);
+ if (missingAnswers.length > 0) {
+ alert("Please answer all required questions.");
+ return;
+ }
+
+ // Submit to backend
+ setLoading(true);
+ fetch(GOOGLE_SCRIPT_URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(answers)
+ })
+ .then(res => res.json())
+ .then(data => {
+ alert("Form submitted successfully!");
+ console.log("Submitted answers:", answers);
+ setLoading(false);
+ })
+ .catch(err => {
+ alert("Failed to submit form. Please try again.");
+ console.error("Submission error:", err);
+ setLoading(false);
+ });
- console.log("Submitted answers:", answers);
- alert("Form submitted! Check console for answers.");
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div style={{ padding: "20px" }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h2>Dynamic Google Form</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <form onSubmit={handleSubmit}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {questions.map((q) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={q.id} style={{ marginBottom: "20px" }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <strong>{q.title}</strong> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <br /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Render inputs based on question type */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {q.type === "TEXT" && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="text" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => handleChange(q.id, e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {q.type === "PARAGRAPH_TEXT" && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <textarea | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => handleChange(q.id, e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ></textarea> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {q.type === "MULTIPLE_CHOICE" && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| q.choices?.map((choice, i) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={i}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="radio" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name={q.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={choice} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => handleChange(q.id, e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {choice} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {q.type === "CHECKBOX" && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| q.choices?.map((choice, i) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={i}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="checkbox" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={choice} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selected = answers[q.id] || []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (e.target.checked) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleChange(q.id, [...selected, choice]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleChange( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| q.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| selected.filter((c) => c !== choice) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {choice} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {q.type === "LIST" && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <select onChange={(e) => handleChange(q.id, e.target.value)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <option value="">Select</option> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {q.choices?.map((choice, i) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <option key={i} value={choice}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {choice} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </option> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </select> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="submit">Submit</button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default DynamicForm; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move the Google Apps Script URL to environment variables.
The script URL is hardcoded, which exposes the endpoint and makes environment-specific configuration difficult.
useEffect(() => { fetch( - "https://script.google.com/macros/s/AKfycbxRamNfuS1b9yBXapdCs9gB3ScRhpxiRPbjTO0qrzeYel0BJEq4VyTGX3GnZ_-KIsZXkw/exec" + process.env.REACT_APP_GOOGLE_SCRIPT_URL )Add to
.env:📝 Committable suggestion
🤖 Prompt for AI Agents