This commit is contained in:
PawelC4
2025-11-09 05:22:03 -05:00
3 changed files with 229 additions and 213 deletions
+102 -41
View File
@@ -3,8 +3,8 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { import {
FormData, FormData,
PROFICIENCY_LEVELS PROFICIENCY_LEVELS
} from './formTypes'; // Ensure path is correct } from './formTypes'; // Ensure path is correct
interface FormPageProps { interface FormPageProps {
@@ -61,6 +61,11 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
}; };
const handleAddStudent = () => { const handleAddStudent = () => {
console.log("Adding student...", formData.students.length);
if( formData.students.length >= 4 ) {
alert("Maxium of 4 teammates allowed.")
return;
}
setFormData({ setFormData({
...formData, ...formData,
students: [...formData.students, { studentName: '', skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }] }] students: [...formData.students, { studentName: '', skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }] }]
@@ -108,17 +113,22 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
}; };
return ( return (
<div className="min-h-screen bg-gray-50 flex items-start justify-center p-4 sm:p-6 font-inter"> <div
<div className="w-full max-w-2xl bg-white p-8 shadow-2xl rounded-xl mt-8"> className="min-h-screen flex items-start justify-center p-4 sm:p-6 font-inter rounded-xl"
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight"> style={{
Generate Project Idea backgroundColor: "#FEEEDF"
}}
>
<div className="w-full max-w-2xl bg-[#FEEEDF] p-2 rounded-xl mt-8">
<h1 className="text-3xl font-extrabold text-center text-[#173C46] mb-8 tracking-tight">
New Project Idea
</h1> </h1>
<form className="space-y-8" onSubmit={handleSubmit}> <form className="space-y-8" onSubmit={handleSubmit}>
<div className="space-y-6 border-b pb-6 border-gray-200"> {/* <div className="space-y-6 border-b pb-6 border-gray-200"> */}
<h2 className="text-xl font-bold text-gray-700">Project Context</h2> {/* <h2 className="text-xl font-bold text-gray-700">Project Context</h2> */}
<div> <div>
<label htmlFor="hackathonName" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="hackathonName" className="block text-xl font-bold text-[#173C46] mb-1">
Hackathon Name Hackathon Name
</label> </label>
<input <input
@@ -126,15 +136,16 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
required required
type="text" type="text"
placeholder="e.g., Google Code Jam" placeholder="e.g., Google Code Jam"
style = {{backgroundColor: "#DEE5D4"}}
name="Hackathon" name="Hackathon"
value={formData.Hackathon} value={formData.Hackathon}
onChange={handleChange} onChange={handleChange}
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 transition duration-150" className="mt-1 block w-full px-4 py-2 rounded-lg shadow-sm transition duration-150"
/> />
</div> </div>
<div> <div>
<label htmlFor="mainTheme" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="mainTheme" className="block text-xl font-bold text-[#173C46] mb-1">
Main Theme Main Theme
</label> </label>
<input <input
@@ -142,17 +153,18 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
required required
type="text" type="text"
placeholder="e.g., Sustainability or AI Ethics" placeholder="e.g., Sustainability or AI Ethics"
style = {{backgroundColor: "#DEE5D4"}}
name="mainTheme" name="mainTheme"
value={formData.mainTheme} value={formData.mainTheme}
onChange={handleChange} onChange={handleChange}
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 transition duration-150" className="mt-1 block w-full px-4 py-2 rounded-lg shadow-sm transition duration-150"
/> />
</div> </div>
</div> {/* </div> */}
<div className="space-y-4 border-b pb-6 border-gray-200"> <div className="space-y-4 border-b pb-6 border-gray-200">
<label className="block text-xl font-bold text-gray-700"> <label className="block text-xl font-bold text-[#173C46] mb-1">
Tracks Stack Hackathon Track(s)
</label> </label>
<div className="space-y-3"> <div className="space-y-3">
{formData.tracksStack.map((tracks, index) => ( {formData.tracksStack.map((tracks, index) => (
@@ -161,10 +173,11 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
type="text" type="text"
required required
placeholder={`Tracks #${index + 1}`} placeholder={`Tracks #${index + 1}`}
style = {{backgroundColor: "#DEE5D4"}}
name={`tracksStack-${index}`} name={`tracksStack-${index}`}
value={tracks} value={tracks}
onChange={(e) => handleTracksChange(e, index)} onChange={(e) => handleTracksChange(e, index)}
className="flex-grow px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-green-500 focus:border-green-500 transition duration-150" className="flex-grow px-4 py-2 rounded-lg shadow-sm transition duration-150"
/> />
{formData.tracksStack.length > 1 && ( {formData.tracksStack.length > 1 && (
<button <button
@@ -183,7 +196,7 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
<button <button
type="button" type="button"
onClick={handleAddTracks} onClick={handleAddTracks}
className="flex items-center justify-center gap-1 py-2 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition duration-150" className="flex items-center justify-center gap-1 py-2 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-[#EA627F] hover:bg-[#d55370] focus:outline-none focus:ring-2 focus:ring-offset-2 transition duration-150"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
Add Tracks Add Tracks
@@ -191,14 +204,33 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<h2 className="text-xl font-bold text-gray-700">Team Members and Skills</h2> <h2 className="text-xl font-bold text-[#173C46]">Teammate(s) and Skills</h2>
<div className="space-y-6"> <div className="space-y-6">
{formData.students.map((student, studentIndex) => ( {formData.students.map((student, studentIndex) => (
<div key={studentIndex} className="p-4 border-2 border-indigo-200 rounded-lg bg-indigo-50 shadow-inner space-y-4"> <div key={studentIndex} className="p-4 rounded-lg bg-[#DEE5D4] shadow-inner space-y-4">
<div className="flex justify-between items-center pb-2 border-b border-indigo-300"> {/* <div className="flex justify-between items-center pb-2 border-b border-[#DEE5D4]"> */}
<h4 className="text-lg font-semibold text-indigo-700">Student #{studentIndex + 1}</h4> {/* <h4 className="text-lg font-semibold text-[#173C46]">Teammate #{studentIndex + 1}:</h4>
<input
type="text"
placeholder="Teammate Name"
// value={student.studentName}
style = {{backgroundColor: "#B8C0 AD"}}
// onChange={(e) => handleStudentNameChange(e, studentIndex)}
// className="w-full px-4 py-2 rounded-lg shadow-sm"
className-="px-3 py-1 text-sm rounded-md"
/> */}
<div className="flex items-center gap-3">
<h4 className="text-lg font-semibold text-[#173C46] whitespace-nowrap">
Teammate #{studentIndex + 1}:
</h4>
<input
type="text"
placeholder="Teammate Name"
className="px-3 py-1 text-sm rounded-md border border-[#B8C0AD] text-[#173C46] w-40"
/>
</div>
{formData.students.length > 1 && ( {formData.students.length > 1 && (
<button <button
type="button" type="button"
@@ -208,22 +240,23 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
Remove Student Remove Student
</button> </button>
)} )}
</div> {/* </div> */}
<div> {/* <div>
<label className="block text-sm font-medium text-indigo-700 mb-1">Student Name</label> <label className="block text-sm font-medium text-[#173C46] mb-1">Teammate Name</label>
<input <input
type="text" type="text"
required required
placeholder="Student Name" placeholder="Teammate Name"
value={student.studentName} value={student.studentName}
style = {{backgroundColor: "#B8C0AD"}}
onChange={(e) => handleStudentNameChange(e, studentIndex)} onChange={(e) => handleStudentNameChange(e, studentIndex)}
className="w-full px-4 py-2 border border-indigo-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500" className="w-full px-4 py-2 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
/> />
</div> </div> */}
<div className="space-y-3 p-3 bg-white rounded-lg border border-indigo-100"> <div className="space-y-3 p-3 bg-[#B8C0AD] rounded-lg ">
<h5 className="text-md font-medium text-indigo-700">Skills</h5> <h5 className="text-md font-medium text-[#173C46]">Skills</h5>
{student.skills.map((skill, skillIndex) => ( {student.skills.map((skill, skillIndex) => (
<div key={skillIndex} className="flex gap-2 items-center"> <div key={skillIndex} className="flex gap-2 items-center">
<input <input
@@ -233,13 +266,13 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
placeholder="Skill (e.g., React, Python)" placeholder="Skill (e.g., React, Python)"
value={skill.skillName} value={skill.skillName}
onChange={(e) => handleSkillChange(e, studentIndex, skillIndex)} onChange={(e) => handleSkillChange(e, studentIndex, skillIndex)}
className="flex-grow px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-purple-500 focus:border-purple-500" className="flex-grow px-3 py-2 bg-[#99A48A] rounded-lg text-sm"
/> />
<select <select
name="proficiency" name="proficiency"
value={skill.proficiency} value={skill.proficiency}
onChange={(e) => handleSkillChange(e, studentIndex, skillIndex)} onChange={(e) => handleSkillChange(e, studentIndex, skillIndex)}
className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-purple-500 focus:border-purple-500 w-1/3" className="px-3 py-2 bg-[#99A48A] rounded-lg text-sm w-1/3"
> >
{PROFICIENCY_LEVELS.map(level => ( {PROFICIENCY_LEVELS.map(level => (
<option key={level} value={level}>{level}</option> <option key={level} value={level}>{level}</option>
@@ -259,7 +292,7 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
<button <button
type="button" type="button"
onClick={() => handleAddSkill(studentIndex)} onClick={() => handleAddSkill(studentIndex)}
className="mt-2 text-xs flex items-center gap-1 py-1 px-3 border border-transparent rounded-lg text-white bg-purple-600 hover:bg-purple-700 transition duration-150" className="mt-2 text-xs flex items-center gap-1 py-1 px-3 border border-transparent rounded-lg text-white bg-[#EA627F] hover:bg-[#d55370] transition duration-150"
> >
+ Add Skill + Add Skill
</button> </button>
@@ -271,21 +304,49 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
<button <button
type="button" type="button"
onClick={handleAddStudent} onClick={handleAddStudent}
className="mt-4 w-full flex items-center justify-center gap-2 py-2 px-4 border border-transparent rounded-lg shadow-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150" disabled={formData.students.length >= 4}
// className="mt-4 w-full flex items-center justify-center gap-2 py-2 px-4 border border-transparent rounded-lg shadow-sm font-medium text-white bg-[#EA627F] hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150"
className={`mt-4 w-full flex items-center justify-center gap-2 py-2 px-4 border border-transparent rounded-lg shadow-sm font-medium text-white transition duration-150
${formData.students.length >= 4
? 'bg-gray-400 cursor-not-allowed'
: 'bg-[#EA627F] hover:bg-[#d55370] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500'}
`}
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path></svg> {/* <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path></svg>
Add Team Member Add Team Member */}
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path>
</svg>
{formData.students.length >= 4 ? "Team Full (4 Max)" : "Add Team Member"}
</button> </button>
</div> </div>
<button <button
type="submit" type="submit"
className="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg text-lg font-semibold text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition duration-150 mt-6" className="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg text-lg font-semibold text-white bg-[#173C46] hover:bg-[#0F3237] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200 ease-in-out"
> >
Generate Idea Create Ideas
</button> </button>
</form>
</form>
{/* this is just removing the test */}
{/* <div className="mt-8 p-4 bg-gray-100 rounded-lg text-sm text-gray-600 border-l-4 border-blue-500">
<p className="font-bold mb-2 text-gray-800">Current Data:</p>
<p><strong>Hackathon:</strong> {formData.Hackathon || '—'}</p>
<p><strong>Main Theme:</strong> {formData.mainTheme || '—'}</p>
<p><strong>Tracks Stack:</strong> {formData.tracksStack.filter(t => t).join(', ') || '—'}</p>
<p className="font-bold mt-2 text-gray-800">Team Members ({formData.students.length}):</p>
<ul className="list-disc ml-5">
{formData.students.map((student, index) => (
<li key={index}>
<strong>{student.studentName || `Student ${index + 1}`}:</strong>
<span className="ml-2">
{student.skills.filter(s => s.skillName).map(s => `${s.skillName} (${s.proficiency})`).join('; ') || 'No skills added'}
</span>
</li>
))}
</ul>
</div> */}
</div> </div>
</div> </div>
); );
+88 -121
View File
@@ -1,13 +1,10 @@
"use client"; "use client";
import React, { useState, useEffect, useCallback, useRef } from "react"; import React, { useState, useEffect, useCallback } from "react";
import FormPage from './FormPage'; import FormPage from './FormPage';
import { CardData } from "../exportType/types";
import SwipeCards from "../components/Cards";
import { ChatSetting } from "../exportType/types"; import { ChatSetting } from "../exportType/types";
import { FormData, PROFICIENCY_LEVELS } from './formTypes'; import { FormData, PROFICIENCY_LEVELS } from './formTypes';
import { saveProject } from '../utils/storage';
import { useRouter } from "next/navigation";
// --- Helper function to get default form data ---
const getDefaultFormData = (): FormData => ({ const getDefaultFormData = (): FormData => ({
Hackathon: '', Hackathon: '',
mainTheme: '', mainTheme: '',
@@ -18,6 +15,7 @@ const getDefaultFormData = (): FormData => ({
}], }],
}); });
// --- New Initializer Function ---
const getInitialState = () => { const getInitialState = () => {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return { return {
@@ -31,8 +29,8 @@ const getInitialState = () => {
localStorage.removeItem('formData'); localStorage.removeItem('formData');
return { return {
data: JSON.parse(storedFormData) as FormData, data: JSON.parse(storedFormData) as FormData,
showForm: true, showForm: false,
runEffect: false, runEffect: true,
}; };
} }
return { return {
@@ -42,160 +40,129 @@ const getInitialState = () => {
}; };
}; };
export default function Home() { // FIX 1 (Memoization): Moved settings OUTSIDE the component.
// This makes it a true constant.
const [formData, setFormData] = useState<FormData>(getDefaultFormData()); const AI_SETTINGS: ChatSetting = {
const [ideaResponse, setIdeaResponse] = useState<string>(""); temperature: 1,
const [generatedCards, setGeneratedCards] = useState<CardData[]>([]); // New state for parsed cards model: "gemini-2.5-pro",
const [initialState] = useState(getInitialState); sysTemInstructions: `You are an elite hackathon idea generator. Your task is to generate five different creative, feasible, and relevant project idea.
const [showForm, setShowForm] = useState(initialState.showForm);
const initialIdeaGenerationAttempted = useRef(false);
const router = useRouter();
const [settings] = useState<ChatSetting>({
temperature: 1,
model: "gemini-2.5-flash-lite",
systemInstructions: `You are an elite hackathon idea generator. Your task is to choose at lease two track and generate five different creative, feasible, and relevant project idea.
The output MUST be in Markdown format and strictly follow this structure: The output MUST be in Markdown format and strictly follow this structure:
### Project Idea Title ### Project Idea Title
**Track** [print out the track you used] **Relevance to Theme:** [Explain briefly]
**Concept:** [A concise, single paragraph description of the product and its primary function.]`, **Core Technology:** [List 3-5 primary technologies]
}); **Team Fit:** [How the team's skills apply]
**Concept:** [A concise, single paragraph description of the product and its primary function.]
**Bonus Feature:** [A single, ambitious feature to impress judges.]`,
};
// ------------------------------------------------
export default function Home() {
const [initialState] = useState(getInitialState);
const [showForm, setShowForm] = useState(initialState.showForm);
const [formData, setFormData] = useState<FormData>(initialState.data);
const [ideaResponse, setIdeaResponse] = useState<string>("");
// The 'settings' state [useState] is now removed from here.
const handleFormSubmitFromFormPage = (submittedData: FormData) => {
setFormData(submittedData);
setShowForm(false);
generateIdeas(submittedData);
};
// Wrapped generateIdeas in useCallback
const generateIdeas = useCallback(async (dataToProcess: FormData) => { const generateIdeas = useCallback(async (dataToProcess: FormData) => {
const parseAiResponse = (markdownString: string): CardData[] => { // 1. Data Cleaning
console.log("Raw AI Response:", markdownString);
const ideas: CardData[] = [];
const ideaBlocks = markdownString.split("### ").filter(block => block.trim() !== "");
ideaBlocks.forEach((block, index) => {
const titleMatch = block.match(/^(.*?)(?=\n\*\*Track\*\*|$)/s);
const trackMatch = block.match(/\*\*Track\*\*\s*:\s*(.*?)(?=\n\*\*Concept\*\*|$)/s);
const conceptMatch = block.match(/\*\*Concept:\*\*\s*(.*)/s);
const title = titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`;
const summary = conceptMatch ? conceptMatch[1].trim() : "No concept provided.";
const tracksRaw = trackMatch ? trackMatch[1].trim() : "";
const tracks = tracksRaw.split(',').map(track => track.trim()).filter(track => track !== '');
ideas.push({
id: index + 1,
title,
tracks,
summary,
});
});
console.log("Parsed Cards:", ideas);
return ideas;
};
const submissionData: FormData = { const submissionData: FormData = {
...dataToProcess, ...dataToProcess,
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''), tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
students: dataToProcess.students students: dataToProcess.students
.map(student => ({ .map(student => ({
...student, ...student,
skills: student.skills.filter(skill => skill.skillName.trim() !== ''), skills: student.skills.filter(skill => skill.skillName.trim() !== ''),
})) }))
.filter(student => student.studentName.trim() !== '' || student.skills.length > 0), .filter(student => student.studentName.trim() !== '' || student.skills.length > 0),
}; };
// 2. Prompt Construction
const prompt = ` const prompt = `
Hackathon: ${submissionData.Hackathon} Hackathon: ${submissionData.Hackathon}
Main Theme: ${submissionData.mainTheme} Main Theme: ${submissionData.mainTheme}
Required Tracks Stack: ${submissionData.tracksStack.join(', ')} Required Tracks Stack: ${submissionData.tracksStack.join(', ')}
Team Members and Skills: ${submissionData.students.map(s => Team Members and Skills: ${submissionData.students.map(s =>
`${s.studentName || 'Unamed Member'} (Skills: ${s.skills.map(sk => `${sk.skillName} - ${sk.proficiency}`).join(', ')})` `${s.studentName || 'Unamed Member'} (Skills: ${s.skills.map(sk => `${sk.skillName} - ${sk.proficiency}`).join(', ')})`
).join('; ')} ).join('; ')}
Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`; Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`;
// 3. API Call
try { try {
setIdeaResponse("Generating ideas... Please wait."); setIdeaResponse("Generating ideas... Please wait.");
setGeneratedCards([]);
const response = await fetch("/api/generate-idea", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: prompt,
settings: settings,
}),
});
const data = await response.json(); const response = await fetch("/api/generate-idea", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
prompt: prompt,
settings: AI_SETTINGS, // Use the constant
}),
});
if (data.error || !response.ok) { const data = await response.json();
console.error("AI Error:", data.error); if (data.error || !response.ok) {
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`); console.error("AI Error:", data.error);
return; setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
} return;
setIdeaResponse(data.response); }
setIdeaResponse(data.response);
const projectId = data.projectId ?? `p-${Date.now()}`;
if (typeof window !== 'undefined') localStorage.setItem('temp_aiResponse', data.response);
router.push(`/cards?projectId=${projectId}&projectTitle=${encodeURIComponent(dataToProcess.Hackathon)}`);
// FIX 2 (Unexpected any): Changed 'any' to 'unknown' and added a type check.
} catch (error: unknown) { } catch (error: unknown) {
console.error("Request Failed:", error); console.error("Request Failed:", error);
let errorMessage = "A network error occurred. Please try again."; let errorMessage = "A network error occurred. Please try again.";
if (error instanceof Error) { if (error instanceof Error) {
errorMessage += ` Details: ${error.message}`; errorMessage += ` Details: ${error.message}`;
} }
setIdeaResponse(errorMessage); setIdeaResponse(errorMessage);
} }
}, [router]); // FIX 3 (Linter): Disable the faulty memoization rule.
// The empty array is correct because setIdeaResponse is a stable state setter.
// eslint-disable-next-line react-hooks/preserve-manual-memoization
}, []);
// Replaced useEffect // Replaced useEffect
useEffect(() => { useEffect(() => {
if (initialState.runEffect) { if (initialState.runEffect) {
//generateIdeas(initialState.data, "some-resumed-id"); generateIdeas(initialState.data);
} }
}, [initialState, generateIdeas]); }, [initialState, generateIdeas]);
const handleFormSubmitFromFormPage = (submittedData: FormData) => {
setShowForm(false);
generateIdeas(submittedData);
};
// --- JSX Rendering --- // --- JSX Rendering ---
return ( return (
<main className="min-h-screen bg-gray-50 flex items-start justify-center p-4 sm:p-6 font-inter"> <main className="min-h-screen flex items-start justify-center p-4 sm:p-6 font-inter" style={{ backgroundColor: "#DEE5D4"}}>
<div className="w-full max-w-2xl bg-white p-8 shadow-2xl rounded-xl mt-8"> <div className="w-full max-w-2xl bg-white p-2 shadow-2xl rounded-xl mt-8" style={{ backgroundColor: "#EA627F"}}>
{showForm ? ( {showForm ? (
<FormPage onFormSubmit={handleFormSubmitFromFormPage} /> <FormPage onFormSubmit={handleFormSubmitFromFormPage} />
) : ( ) : (
<> <>
{generatedCards.length > 0 ? ( <h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight">
<SwipeCards initialCards={generatedCards} /> Generated Project Idea 💡
) : ( </h1>
<> <div className="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight"> <h2 className="text-xl font-bold text-gray-700 mb-2">Generated Ideas:</h2>
Generated Project Idea 💡 <div
</h1>
<div className="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
<h2 className="text-xl font-bold text-gray-700 mb-2">Generated Ideas:</h2>
<div
className="prose max-w-none" className="prose max-w-none"
dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }} dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }}
/> />
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>} {ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
</div> </div>
</>
)}
<button <button
onClick={() => { onClick={() => setShowForm(true)}
setShowForm(true);
if(typeof window !== 'undefined') localStorage.removeItem('formData');
setFormData(getDefaultFormData());
}
}
className="mt-6 w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg text-lg font-semibold text-white bg-blue-600 hover:bg-blue-700 transition duration-150" className="mt-6 w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg text-lg font-semibold text-white bg-blue-600 hover:bg-blue-700 transition duration-150"
> >
Create New Project Go Back to Form
</button> </button>
</> </>
)} )}
-12
View File
@@ -66,7 +66,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@@ -1471,7 +1470,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
@@ -1528,7 +1526,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz",
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/scope-manager": "8.46.3",
"@typescript-eslint/types": "8.46.3", "@typescript-eslint/types": "8.46.3",
@@ -2026,7 +2023,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2345,7 +2341,6 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.19", "baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751", "caniuse-lite": "^1.0.30001751",
@@ -2875,7 +2870,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -3053,7 +3047,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@rtsao/scc": "^1.1.0", "@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9", "array-includes": "^3.1.9",
@@ -5130,7 +5123,6 @@
"version": "19.2.0", "version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -5139,7 +5131,6 @@
"version": "19.2.0", "version": "19.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
@@ -5785,7 +5776,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -5937,7 +5927,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -6198,7 +6187,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"dev": true, "dev": true,
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }