added images, changed css for homepage, dashboard and got rid of templates for cards and made them take actual local variables

This commit is contained in:
Ivan Rudik
2025-11-09 12:26:45 +00:00
parent 8f08b053f2
commit cd28b6ca93
12 changed files with 112 additions and 105 deletions
+43 -13
View File
@@ -1,20 +1,27 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { getStoredProjects, StoredProject } from "../utils/storage"; import { getStoredProjects, StoredProject,setProjectForEditing } from "../utils/storage";
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from "next/navigation";
export default function Dashboard() { export default function Dashboard() {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const [sideBarOpen, setSidebarOpen] = useState(true); const [sideBarOpen, setSidebarOpen] = useState(true);
const [sortOrder, setSortOrder] = useState("newest"); const [sortOrder, setSortOrder] = useState("newest");
const [projects, setProjects] = useState<StoredProject[]>([]); const [projects, setProjects] = useState<StoredProject[]>([]);
const router = useRouter();
useEffect(() => { useEffect(() => {
const storedProjects = getStoredProjects(); const storedProjects = getStoredProjects();
setProjects(storedProjects); setProjects(storedProjects);
}, []); }, []);
const handleRerun = (project: StoredProject) => {
setProjectForEditing(project);
router.push('/formfile');
};
const sortedProjects = [...projects].sort((a, b) => { const sortedProjects = [...projects].sort((a, b) => {
const dateA = new Date(a.id).getTime(); const dateA = new Date(a.id).getTime();
const dateB = new Date(b.id).getTime(); const dateB = new Date(b.id).getTime();
@@ -39,15 +46,17 @@ export default function Dashboard() {
}; };
return ( return (
<div className="flex min-h-screen bg-zinc-50 dark:bg-black font-sans transition-all duration-300"> <div className="flex min-h-screen bg-zinc-50 font-sans transition-all duration-300"
style={{ backgroundColor: '#FFEAE1' }}>
<aside <aside
className={`${sideBarOpen ? "w-64" : "w-16" className={`${sideBarOpen ? "w-64" : "w-16"
} bg-white dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 transition-all duration-300 flex flex-col`} } transition-all duration-300 flex flex-col `}
style={{ backgroundColor: 'rgba(153, 164, 138, 0.9)' }}
> >
<div className="flex items-center justify-between p-4 border-b border-zinc-200 dark:border-zinc-800"> <div className="flex items-center justify-between p-4">
{sideBarOpen && ( {sideBarOpen && (
<span className="text-lg font-semibold text-zinc-800 dark:text-zinc-100"> <span className="text-lg font-semibold text-zinc-800 dark:text-zinc-100">
More SparkBot
</span> </span>
)} )}
<button <button
@@ -85,7 +94,7 @@ export default function Dashboard() {
<div className="flex-1 flex flex-col min-w-0"> <div className="flex-1 flex flex-col min-w-0">
<header <header
className="sticky top-0 z-10 backdrop-blur-sm border-b" className="sticky top-0 z-10 backdrop-blur-sm"
style={{ backgroundColor: 'rgba(153, 164, 138, 0.9)', borderColor: '#8A947E' }} // Top bar background (semi-transparent) and border style={{ backgroundColor: 'rgba(153, 164, 138, 0.9)', borderColor: '#8A947E' }} // Top bar background (semi-transparent) and border
> >
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between gap-4"> <div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between gap-4">
@@ -134,16 +143,37 @@ export default function Dashboard() {
<section> <section>
<ul className="space-y-3"> <ul className="space-y-3">
{filtered.map((project, index) => ( {filtered.map((project) => (
<li <li
key={index} key={project.id}
className="flex justify-between p-4 border rounded-md" className="flex flex-col sm:flex-row justify-between items-start sm:items-center p-4 border rounded-md"
style={{ backgroundColor: '#B8C0AD', color: '#222', borderColor: '#A9B29E' }} // Project boxes style={{ backgroundColor: '#B8C0AD', color: '#222', borderColor: '#A9B29E' }}
> >
<span>{project.name}</span> <div className="flex-1 mb-2 sm:mb-0">
<span className="text-gray-700 text-sm"> <Link
Created: {project.createdAt} href={`/chatbot?projectId=${encodeURIComponent(project.id)}&projectTitle=${encodeURIComponent(project.title)}`}
className="text-lg font-semibold text-[#173C46] hover:underline"
>
{project.title}
</Link>
<span className="block text-gray-700 text-sm mt-1">
Created: {new Date(project.id).toLocaleString()}
</span> </span>
</div>
<div className="flex gap-2">
<button
onClick={() => handleRerun(project)}
className="px-3 py-1 text-sm font-medium rounded-md shadow hover:opacity-90"
style={{
color: 'white',
backgroundColor: '#EA627F'
}}
>
Re-run
</button>
</div>
</li> </li>
))} ))}
{filtered.length === 0 && ( {filtered.length === 0 && (
+10 -10
View File
@@ -26,9 +26,13 @@ const parseAiResponse = (markdownString: string): CardData[] => {
ideaBlocks.forEach((block, index) => { ideaBlocks.forEach((block, index) => {
const titleMatch = block.match(/^(.*?)\n/); const titleMatch = block.match(/^(.*?)\n/);
const conceptMatch = block.match(/\*\*Concept:\*\*\s*(.*)/s); const conceptMatch = block.match(/\*\*Concept:\*\*\s*([\s\S]*?)(?=\n\*\*|$)/s); // Improved summary regex
const tracksMatch = block.matchAll(/\*\*(?!Concept|Relevance|Core|Team|Bonus)(.*?):\*\*/g);
const tracks = [...tracksMatch].map(match => match[1].trim()); // --- THIS IS THE FIX ---
// It now looks for "**Core Technology:**" and splits the technologies by comma
const techMatch = block.match(/\*\*Core Technology:\*\*\s*(.*)/);
const tracks = techMatch ? techMatch[1].split(',').map(t => t.trim()) : ["General"];
// -----------------------
ideas.push({ ideas.push({
id: index + 1, id: index + 1,
@@ -60,13 +64,9 @@ export default function CardsPage() {
setCards(parsedCards.reverse()); setCards(parsedCards.reverse());
setInitialCards(parsedCards.reverse()); setInitialCards(parsedCards.reverse());
} else { } else {
console.warn("No AI response found. Using hardcoded cards as fallback."); // If no AI response is found, we do nothing.
const hardcodedCards: CardData[] = [ // The 'cards' state will remain empty, showing the "No more cards!" UI.
{ id: 1, title: "AI Project Idea 1", summary: "A cool project...", tracks: ["AI", "Web"] }, console.warn("No 'temp_aiResponse' found in localStorage. Displaying empty deck.");
{ id: 2, title: "AI Project Idea 2", summary: "Another cool project...", tracks: ["Mobile", "Game"] },
];
setCards(hardcodedCards.reverse());
setInitialCards(hardcodedCards.reverse());
} }
setIsLoading(false); setIsLoading(false);
}, []); }, []);
+1 -1
View File
@@ -41,7 +41,7 @@ function ChatbotContent() {
const historyRef = useRef<ChatHistory>(history); const historyRef = useRef<ChatHistory>(history);
const [settings] = useState<ChatSetting>({ const [settings] = useState<ChatSetting>({
temperature: 1, temperature: 1,
model: "gemini-2.5-pro", model: "gemini-2.5-flash-lite",
systemInstructions: "you are a ai helper. Your goal is to expand the project idea and interact with the user on any problem or question they have with the project. You are not allow to code the project for them and stray away from your job" systemInstructions: "you are a ai helper. Your goal is to expand the project idea and interact with the user on any problem or question they have with the project. You are not allow to code the project for them and stray away from your job"
}); });
+15 -10
View File
@@ -1,8 +1,10 @@
"use client"; "use client";
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
import FormPage from './FormPage'; import FormPage from './FormPage';
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";
// --- Helper function to get default form data --- // --- Helper function to get default form data ---
const getDefaultFormData = (): FormData => ({ const getDefaultFormData = (): FormData => ({
@@ -44,8 +46,8 @@ const getInitialState = () => {
// This makes it a true constant. // This makes it a true constant.
const AI_SETTINGS: ChatSetting = { const AI_SETTINGS: ChatSetting = {
temperature: 1, temperature: 1,
model: "gemini-2.5-pro", model: "gemini-2.5-flash-lite",
sysTemInstructions: `You are an elite hackathon idea generator. Your task is to generate five different creative, feasible, and relevant project idea. 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: The output MUST be in Markdown format and strictly follow this structure:
### Project Idea Title ### Project Idea Title
**Relevance to Theme:** [Explain briefly] **Relevance to Theme:** [Explain briefly]
@@ -58,7 +60,7 @@ The output MUST be in Markdown format and strictly follow this structure:
// ------------------------------------------------ // ------------------------------------------------
export default function Home() { export default function Home() {
const router = useRouter();
const [initialState] = useState(getInitialState); const [initialState] = useState(getInitialState);
const [showForm, setShowForm] = useState(initialState.showForm); const [showForm, setShowForm] = useState(initialState.showForm);
const [formData, setFormData] = useState<FormData>(initialState.data); const [formData, setFormData] = useState<FormData>(initialState.data);
@@ -75,6 +77,10 @@ export default function Home() {
// Wrapped generateIdeas in useCallback // Wrapped generateIdeas in useCallback
const generateIdeas = useCallback(async (dataToProcess: FormData) => { const generateIdeas = useCallback(async (dataToProcess: FormData) => {
// 1. Data Cleaning // 1. Data Cleaning
const newProject = saveProject(dataToProcess);
const projectId = newProject.id;
const projectTitle = newProject.title;
const submissionData: FormData = { const submissionData: FormData = {
...dataToProcess, ...dataToProcess,
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''), tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
@@ -94,8 +100,7 @@ export default function Home() {
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.`;
// 3. API Call // 3. API Call
try { try {
@@ -116,7 +121,10 @@ export default function Home() {
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`); setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
return; return;
} }
setIdeaResponse(data.response); localStorage.setItem('temp_aiResponse', data.response);
// Redirect to the cards page with the new project ID and title
router.push(`/cards?projectId=${encodeURIComponent(projectId)}&projectTitle=${encodeURIComponent(projectTitle)}`);
// FIX 2 (Unexpected any): Changed 'any' to 'unknown' and added a type check. // FIX 2 (Unexpected any): Changed 'any' to 'unknown' and added a type check.
} catch (error: unknown) { } catch (error: unknown) {
@@ -127,10 +135,7 @@ export default function Home() {
} }
setIdeaResponse(errorMessage); setIdeaResponse(errorMessage);
} }
// FIX 3 (Linter): Disable the faulty memoization rule. }, [router]);
// 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(() => {
-35
View File
@@ -1,32 +1,3 @@
<<<<<<< HEAD
=======
/* @import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
:root {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
} */
/* 1. Import BOTH fonts in one go */
@import url('https://fonts.googleapis.com/css2?family=Concert+One&family=Pixelify+Sans:wght@400;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Concert+One&family=Pixelify+Sans:wght@400;700&display=swap');
@import "tailwindcss"; @import "tailwindcss";
@@ -41,15 +12,9 @@ body {
/* 3. Assign fonts to Tailwind's theme */ /* 3. Assign fonts to Tailwind's theme */
<<<<<<< HEAD
/* Tell Tailwind to use the variables already defined in app/layout.tsx */
--font-sans: var(--font-sans);
--font-header: var(--font-header);
=======
/* --font-sans is for regular text (Concert One) */ /* --font-sans is for regular text (Concert One) */
--font-sans: var(--font-concert-one); --font-sans: var(--font-concert-one);
--font-header: var(--font-pixelify); --font-header: var(--font-pixelify);
>>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
} }
+32 -25
View File
@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
// import Image from "next/image"; // Removed Next.js import // import Image from "next/image"; // Removed Next.js import
// import Link from "next/link"; // Removed Next.js import import Link from "next/link"; // Removed Next.js import
export default function Home() { export default function Home() {
return ( return (
@@ -21,9 +22,6 @@ export default function Home() {
<Link href="/Dashboard" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"> <Link href="/Dashboard" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
Dashboard Dashboard
</Link> </Link>
<Link href="/cards" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
Cards
</Link>
</div> </div>
</header> </header>
@@ -35,7 +33,7 @@ export default function Home() {
style={{ color: "#173C46" }} style={{ color: "#173C46" }}
className="max-w-xs text-5xl font-semibold leading-tight tracking-tight text-black" className="max-w-xs text-5xl font-semibold leading-tight tracking-tight text-black"
> >
Welcome! This is HackSpark Welcome! This is SparkBot
</h1> </h1>
<p <p
style={{ color: "#173C46" }} style={{ color: "#173C46" }}
@@ -44,12 +42,11 @@ export default function Home() {
Got a hackathon coming up? Tell us your track, your team’s skills, what you’re passionate about, and more — and we’ll provide you with the perfect project idea!{" "} Got a hackathon coming up? Tell us your track, your team’s skills, what you’re passionate about, and more — and we’ll provide you with the perfect project idea!{" "}
</p> </p>
<Link <button
href="/Dashboard"
style={{ backgroundColor: "#173C46", color: "white" }} style={{ backgroundColor: "#173C46", color: "white" }}
className="inline-block px-5 py-2.5 text-lg rounded-md shadow-md font-semibold hover:opacity-90 transition-opacity" className="inline-block px-5 py-2.5 text-lg rounded-md shadow-md font-semibold hover:opacity-90 transition-opacity"
> >
<Link href="/dashboard">Get Started</Link> <Link href="/Dashboard">Get Started</Link>
</button> </button>
</div> </div>
@@ -58,7 +55,12 @@ export default function Home() {
className="w-full max-w-[450px] h-[350px] rounded-xl shadow-md flex items-center justify-center text-white" className="w-full max-w-[450px] h-[350px] rounded-xl shadow-md flex items-center justify-center text-white"
style={{ backgroundColor: "#173C46" }} style={{ backgroundColor: "#173C46" }}
> >
Image Placeholder <img
src="s1.jpeg"
alt=" Img"
width={350}
height={350}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -81,11 +83,8 @@ export default function Home() {
style={{ backgroundColor: "#B8C0AD" }} style={{ backgroundColor: "#B8C0AD" }}
> >
<h2 className="text-3xl font-semibold text-black mb-3"> <h2 className="text-3xl font-semibold text-black mb-3">
First Text Block Input your hackathon preferences and team skills to get tailored project ideas.
</h2> </h2>
<p className="text-xl text-zinc-800">
This is the content for the first text block. You can write anything here.
</p>
</div> </div>
{/* Box 2 (Image) - Standardized Size */} {/* Box 2 (Image) - Standardized Size */}
<div className="w-full max-w-lg flex justify-center items-center"> <div className="w-full max-w-lg flex justify-center items-center">
@@ -93,7 +92,12 @@ export default function Home() {
className="w-full h-[350px] rounded-xl shadow-md flex items-center justify-center text-zinc-800" className="w-full h-[350px] rounded-xl shadow-md flex items-center justify-center text-zinc-800"
style={{ backgroundColor: "#B8C0AD" }} style={{ backgroundColor: "#B8C0AD" }}
> >
Image Placeholder <img
src="s2.jpeg"
alt=" Img"
width={400}
height={400}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -106,7 +110,12 @@ export default function Home() {
className="w-full h-[350px] rounded-xl shadow-md flex items-center justify-center text-zinc-800" className="w-full h-[350px] rounded-xl shadow-md flex items-center justify-center text-zinc-800"
style={{ backgroundColor: "#B8C0AD" }} style={{ backgroundColor: "#B8C0AD" }}
> >
Image Placeholder <img
src="s3.jpeg"
alt=" Img"
width={400}
height={400}
/>
</div> </div>
</div> </div>
{/* Box 4 (Text) - Standardized Size */} {/* Box 4 (Text) - Standardized Size */}
@@ -116,12 +125,8 @@ export default function Home() {
style={{ backgroundColor: "#B8C0AD" }} style={{ backgroundColor: "#B8C0AD" }}
> >
<h2 className="text-3xl font-semibold text-black mb-3"> <h2 className="text-3xl font-semibold text-black mb-3">
Second Text Block Choose between generated project ideas by swiping left or right on the cards.
</h2> </h2>
{/* Fixed the </D> typo to </p> */}
<p className="text-xl text-zinc-800">
This is the content for the second text block. You can write anything here.
</p>
</div> </div>
</div> </div>
</div> </div>
@@ -134,11 +139,8 @@ export default function Home() {
style={{ backgroundColor: "#B8C0AD" }} style={{ backgroundColor: "#B8C0AD" }}
> >
<h2 className="text-3xl font-semibold text-black mb-3"> <h2 className="text-3xl font-semibold text-black mb-3">
Third Text Block Work on your chosen project idea with your team using our integrated chat-based AI assistant.
</h2> </h2>
<p className="text-xl text-zinc-800">
This is the content for the third text block. You can write anything here.
</p>
</div> </div>
{/* Box 6 (Image) - Standardized Size */} {/* Box 6 (Image) - Standardized Size */}
<div className="w-full max-w-lg flex justify-center items-center"> <div className="w-full max-w-lg flex justify-center items-center">
@@ -146,7 +148,12 @@ export default function Home() {
className="w-full h-[350px] rounded-xl shadow-md flex items-center justify-center text-zinc-800" className="w-full h-[350px] rounded-xl shadow-md flex items-center justify-center text-zinc-800"
style={{ backgroundColor: "#B8C0AD" }} style={{ backgroundColor: "#B8C0AD" }}
> >
Image Placeholder <img
src="s5.jpeg"
alt=" Img"
width={420}
height={450}
/>
</div> </div>
</div> </div>
</div> </div>
+2 -2
View File
@@ -15,8 +15,8 @@ export async function chatToGemini(
settings:ChatSetting settings:ChatSetting
):Promise<string>{ ):Promise<string>{
const model = genAI.getGenerativeModel({ const model = genAI.getGenerativeModel({
model:settings.model || "gemini-2.5-flash", model:settings.model || "gemini-2.5-flash-lite",
systemInstruction: settings.systemInstructions || "you are going to create a project idea, explain it in summery first before going to detail", systemInstruction: settings.systemInstructions || "you are going to create a project idea, explain it in summary first before going to detail",
}); });
const generationConfig:GenerateConfig = { const generationConfig:GenerateConfig = {
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB