Commiting Stylized formpage

This commit is contained in:
Vanna Feng
2025-11-09 10:18:06 +00:00
parent ae0ee812c5
commit ca5b2291e8
3 changed files with 229 additions and 213 deletions
+119 -152
View File
@@ -1,29 +1,27 @@
"use client";
import React, { useState, useEffect, useCallback, useRef } from "react";
import React, { useState, useEffect, useCallback } from "react";
import FormPage from './FormPage';
import { CardData } from "../exportType/types";
import SwipeCards from "../components/Cards";
import { ChatSetting } from "../exportType/types";
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 => ({
Hackathon: '',
mainTheme: '',
tracksStack: [''],
students: [{
studentName: '',
skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }]
students: [{
studentName: '',
skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }]
}],
});
// --- New Initializer Function ---
const getInitialState = () => {
if (typeof window === 'undefined') {
return {
data: getDefaultFormData(),
showForm: true,
runEffect: false
return {
data: getDefaultFormData(),
showForm: true,
runEffect: false
};
}
const storedFormData = localStorage.getItem('formData');
@@ -31,171 +29,140 @@ const getInitialState = () => {
localStorage.removeItem('formData');
return {
data: JSON.parse(storedFormData) as FormData,
showForm: true,
runEffect: false,
showForm: false,
runEffect: true,
};
}
return {
data: getDefaultFormData(),
showForm: true,
runEffect: false
return {
data: getDefaultFormData(),
showForm: true,
runEffect: false
};
};
export default function Home() {
const [formData, setFormData] = useState<FormData>(getDefaultFormData());
const [ideaResponse, setIdeaResponse] = useState<string>("");
const [generatedCards, setGeneratedCards] = useState<CardData[]>([]); // New state for parsed cards
const [initialState] = useState(getInitialState);
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.
// FIX 1 (Memoization): Moved settings OUTSIDE the component.
// This makes it a true constant.
const AI_SETTINGS: ChatSetting = {
temperature: 1,
model: "gemini-2.5-pro",
sysTemInstructions: `You are an elite hackathon idea generator. Your task is to generate five different creative, feasible, and relevant project idea.
The output MUST be in Markdown format and strictly follow this structure:
### Project Idea Title
**Track** [print out the track you used]
**Concept:** [A concise, single paragraph description of the product and its primary function.]`,
});
**Relevance to Theme:** [Explain briefly]
**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.]`,
};
const generateIdeas = useCallback(async (dataToProcess: FormData) => {
const parseAiResponse = (markdownString: string): CardData[] => {
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 = {
...dataToProcess,
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
students: dataToProcess.students
.map(student => ({
...student,
skills: student.skills.filter(skill => skill.skillName.trim() !== ''),
}))
.filter(student => student.studentName.trim() !== '' || student.skills.length > 0),
};
const prompt = `
Hackathon: ${submissionData.Hackathon}
Main Theme: ${submissionData.mainTheme}
Required Tracks Stack: ${submissionData.tracksStack.join(', ')}
Team Members and Skills: ${submissionData.students.map(s =>
`${s.studentName || 'Unamed Member'} (Skills: ${s.skills.map(sk => `${sk.skillName} - ${sk.proficiency}`).join(', ')})`
).join('; ')}
Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`;
try {
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();
if (data.error || !response.ok) {
console.error("AI Error:", data.error);
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
return;
}
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)}`);
} catch (error: unknown) {
console.error("Request Failed:", error);
let errorMessage = "A network error occurred. Please try again.";
if (error instanceof Error) {
errorMessage += ` Details: ${error.message}`;
}
setIdeaResponse(errorMessage);
}
}, [router]);
// Replaced useEffect
useEffect(() => {
if (initialState.runEffect) {
//generateIdeas(initialState.data, "some-resumed-id");
}
}, [initialState, generateIdeas]);
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) => {
// 1. Data Cleaning
const submissionData: FormData = {
...dataToProcess,
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
students: dataToProcess.students
.map(student => ({
...student,
skills: student.skills.filter(skill => skill.skillName.trim() !== ''),
}))
.filter(student => student.studentName.trim() !== '' || student.skills.length > 0),
};
// 2. Prompt Construction
const prompt = `
Hackathon: ${submissionData.Hackathon}
Main Theme: ${submissionData.mainTheme}
Required Tracks Stack: ${submissionData.tracksStack.join(', ')}
Team Members and Skills: ${submissionData.students.map(s =>
`${s.studentName || 'Unamed Member'} (Skills: ${s.skills.map(sk => `${sk.skillName} - ${sk.proficiency}`).join(', ')})`
).join('; ')}
Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`;
// 3. API Call
try {
setIdeaResponse("Generating ideas... Please wait.");
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
}),
});
const data = await response.json();
if (data.error || !response.ok) {
console.error("AI Error:", data.error);
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
return;
}
setIdeaResponse(data.response);
// FIX 2 (Unexpected any): Changed 'any' to 'unknown' and added a type check.
} catch (error: unknown) {
console.error("Request Failed:", error);
let errorMessage = "A network error occurred. Please try again.";
if (error instanceof Error) {
errorMessage += ` Details: ${error.message}`;
}
setIdeaResponse(errorMessage);
}
// 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
useEffect(() => {
if (initialState.runEffect) {
generateIdeas(initialState.data);
}
}, [initialState, generateIdeas]);
// --- JSX Rendering ---
return (
<main className="min-h-screen bg-gray-50 flex items-start justify-center p-4 sm:p-6 font-inter">
<div className="w-full max-w-2xl bg-white p-8 shadow-2xl rounded-xl mt-8">
<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-2 shadow-2xl rounded-xl mt-8" style={{ backgroundColor: "#EA627F"}}>
{showForm ? (
<FormPage onFormSubmit={handleFormSubmitFromFormPage} />
) : (
<>
{generatedCards.length > 0 ? (
<SwipeCards initialCards={generatedCards} />
) : (
<>
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight">
Generated Project Idea 💡
</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"
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight">
Generated Project Idea 💡
</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"
dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }}
/>
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
</div>
</>
)}
/>
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
</div>
<button
onClick={() => {
setShowForm(true);
if(typeof window !== 'undefined') localStorage.removeItem('formData');
setFormData(getDefaultFormData());
}
}
onClick={() => setShowForm(true)}
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>
</>
)}