mirror of
https://github.com/PinkSheep27/SBU-Hackathon.git
synced 2026-10-10 23:50:26 -04:00
Merge branch 'main' of https://github.com/PinkSheep27/SBU-Hackathon
This commit is contained in:
+65
-78
@@ -1,80 +1,66 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
// import { useRouter } from "next/navigation"; // Removed Next.js import
|
import { getStoredProjects, StoredProject } from "../utils/storage";
|
||||||
// import Link from 'next/link'; // Removed Next.js import
|
import Link from 'next/link';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
// const router = useRouter(); // Removed Next.js hook
|
const [query, setQuery] = useState("");
|
||||||
const [query, setQuery] = useState("");
|
const [sideBarOpen, setSidebarOpen] = useState(true);
|
||||||
const [sideBarOpen, setSidebarOpen] = useState(true);
|
const [sortOrder, setSortOrder] = useState("newest");
|
||||||
// 1. Add state to track sort order
|
const [projects, setProjects] = useState<StoredProject[]>([]);
|
||||||
const [sortOrder, setSortOrder] = useState("newest"); // 'newest' or 'oldest'
|
|
||||||
|
|
||||||
const projects = [
|
useEffect(() => {
|
||||||
{ name: "Alpha Project", createdAt: "2025-01-01" },
|
const storedProjects = getStoredProjects();
|
||||||
{ name: "Beta Board", createdAt: "2025-02-15" },
|
setProjects(storedProjects);
|
||||||
{ name: "Gamma Tasl", createdAt: "2025-03-10" },
|
}, []);
|
||||||
];
|
|
||||||
|
|
||||||
// 2. Update sorting logic to use the sortOrder state
|
const sortedProjects = [...projects].sort((a, b) => {
|
||||||
const sortedProjects = [...projects].sort((a, b) => {
|
const dateA = new Date(a.id).getTime();
|
||||||
// Get numeric timestamps to avoid TypeScript error
|
const dateB = new Date(b.id).getTime();
|
||||||
const dateA = new Date(a.createdAt).getTime();
|
|
||||||
const dateB = new Date(b.createdAt).getTime();
|
|
||||||
|
|
||||||
if (sortOrder === "newest") {
|
if (sortOrder === "newest") {
|
||||||
return dateB - dateA; // Newest first
|
return dateB - dateA;
|
||||||
} else {
|
} else {
|
||||||
return dateA - dateB; // Oldest first
|
return dateA - dateB;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. Filter the *sorted* array based on the query
|
const filtered = sortedProjects.filter((p) =>
|
||||||
const filtered = sortedProjects.filter((p) =>
|
p.title.toLowerCase().includes(query.toLowerCase())
|
||||||
p.name.toLowerCase().includes(query.toLowerCase())
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// Helper function to handle navigation for the "Create" button
|
const handleCreateClick = () => {
|
||||||
const handleCreateClick = () => {
|
window.location.href = "/formfile";
|
||||||
// Use standard window navigation instead of Next.js router
|
};
|
||||||
window.location.href = "/formfile";
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4. Function to toggle the sort order
|
const toggleSortOrder = () => {
|
||||||
const toggleSortOrder = () => {
|
setSortOrder(currentOrder => (currentOrder === "newest" ? "oldest" : "newest"));
|
||||||
setSortOrder(currentOrder => (currentOrder === "newest" ? "oldest" : "newest"));
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex min-h-screen bg-zinc-50 dark:bg-black font-sans transition-all duration-300">
|
||||||
className="flex min-h-screen font-sans transition-all duration-300"
|
<aside
|
||||||
style={{ backgroundColor: '#FEEEDF' }} // Main background
|
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`}
|
||||||
<aside
|
>
|
||||||
className={`${sideBarOpen ? "w-64" : "w-16"
|
<div className="flex items-center justify-between p-4 border-b border-zinc-200 dark:border-zinc-800">
|
||||||
} transition-all duration-300 flex flex-col`}
|
{sideBarOpen && (
|
||||||
style={{ backgroundColor: '#99A48A'}} // Sidebar background and border
|
<span className="text-lg font-semibold text-zinc-800 dark:text-zinc-100">
|
||||||
>
|
More
|
||||||
<div
|
</span>
|
||||||
className="flex items-center justify-between p-4 "
|
)}
|
||||||
>
|
<button
|
||||||
{sideBarOpen && (
|
onClick={() => setSidebarOpen(!sideBarOpen)}
|
||||||
<span className="text-5xl font-semibold text-white">
|
className="text-zinc-600 dark:text-zinc-300 hover:text-blue-500 transition-colors"
|
||||||
More
|
title="Toggle Sidebar"
|
||||||
</span>
|
>
|
||||||
)}
|
☰
|
||||||
<button
|
</button>
|
||||||
onClick={() => setSidebarOpen(!sideBarOpen)}
|
</div>
|
||||||
className="text-white hover:text-gray-200 transition-colors text-3xl"
|
|
||||||
title="Toggle Sidebar"
|
|
||||||
>
|
|
||||||
☰
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
className={`flex flex-col text-3xl p-4 gap-4 text-white transition-opacity duration-200 ${sideBarOpen ? "opacity-100" : "opacity-0"
|
className={`flex flex-col p-4 gap-3 text-zinc-700 dark:text-zinc-300 transition-opacity duration-200 ${sideBarOpen ? "opacity-100" : "opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -84,7 +70,7 @@ export default function Dashboard() {
|
|||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
<p
|
<p
|
||||||
className="hover:text-gray-200 transition-colors cursor-pointer"
|
className="hover:text-gray-200 transition-colors"
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</p>
|
</p>
|
||||||
@@ -99,8 +85,8 @@ 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 "
|
className="sticky top-0 z-10 backdrop-blur-sm border-b"
|
||||||
style={{ backgroundColor: '#DEE5D4'}} // 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">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -109,7 +95,7 @@ export default function Dashboard() {
|
|||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder="Search projects, boards or tasks..."
|
placeholder="Search projects, boards or tasks..."
|
||||||
className="w-full max-w-md px-5 py-3 text-lg rounded-md border text-black focus:outline-none focus:ring-2 focus:ring-[#EA627F]"
|
className="w-full max-w-md px-4 py-2 rounded-md border text-black focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
style={{ backgroundColor: '#F7F7F7', borderColor: '#D1D1D1' }} // Search bar "off-white"
|
style={{ backgroundColor: '#F7F7F7', borderColor: '#D1D1D1' }} // Search bar "off-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,7 +104,7 @@ export default function Dashboard() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleCreateClick} // Use the new handler
|
onClick={handleCreateClick} // Use the new handler
|
||||||
className="text-white px-6 py-3 text-xl rounded-md shadow-sm hover:opacity-90 hover:scale-[.98] transition-all duration-150"
|
className="text-white px-4 py-2 rounded-md shadow-sm hover:opacity-90"
|
||||||
style={{ backgroundColor: '#EA627F' }} // Create button
|
style={{ backgroundColor: '#EA627F' }} // Create button
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
@@ -128,17 +114,18 @@ export default function Dashboard() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="max-w-7xl w-full mx-auto px-6 py-10">
|
<main className="max-w-7xl w-full mx-auto px-6 py-10">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h1 className="text-4xl font-semibold text-black">
|
<h1 className="text-2xl font-semibold text-black">
|
||||||
Project Names
|
Project Names
|
||||||
</h1>
|
</h1>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleSortOrder}
|
onClick={toggleSortOrder}
|
||||||
className="px-4 py-2 text-lg font-medium rounded-md"
|
className="px-3 py-1 text-sm font-medium border rounded-md"
|
||||||
style={{
|
style={{
|
||||||
color: '#333',
|
color: '#333',
|
||||||
backgroundColor: '#F7F7F7', // "off-white"
|
backgroundColor: '#F7F7F7', // "off-white"
|
||||||
|
borderColor: '#D1D1D1'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sort by: {sortOrder === 'newest' ? 'Newest' : 'Oldest'}
|
Sort by: {sortOrder === 'newest' ? 'Newest' : 'Oldest'}
|
||||||
@@ -146,21 +133,21 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<ul className="space-y-4">
|
<ul className="space-y-3">
|
||||||
{filtered.map((project, index) => (
|
{filtered.map((project, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className="flex justify-between items-center p-6 rounded-md transition-all duration-200 ease-in-out hover:scale-[1.02] hover:shadow-lg"
|
className="flex justify-between p-4 border rounded-md"
|
||||||
style={{ backgroundColor: '#B8C0AD', color: '#222'}} // Project boxes
|
style={{ backgroundColor: '#B8C0AD', color: '#222', borderColor: '#A9B29E' }} // Project boxes
|
||||||
>
|
>
|
||||||
<span className="text-xl">{project.name}</span>
|
<span>{project.name}</span>
|
||||||
<span className="text-gray-700 text-lg">
|
<span className="text-gray-700 text-sm">
|
||||||
Created: {project.createdAt}
|
Created: {project.createdAt}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{filtered.length === 0 && (
|
{filtered.length === 0 && (
|
||||||
<li className="text-lg text-gray-600">No results found</li>
|
<li className="text-gray-600">No results found</li>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -8,17 +8,19 @@ export async function POST(request: Request) {
|
|||||||
const { userMessage, history, settings } = await request.json() as {
|
const { userMessage, history, settings } = await request.json() as {
|
||||||
userMessage: string;
|
userMessage: string;
|
||||||
history: ChatHistory;
|
history: ChatHistory;
|
||||||
settings: ChatSetting;
|
settings?: ChatSetting; // settings is optional from the request
|
||||||
};
|
};
|
||||||
|
|
||||||
const aiResponse = await chatToGemini(userMessage, history, settings);
|
// Call the Gemini helper function
|
||||||
return NextResponse.json({ response: aiResponse });
|
const geminiResponse = await chatToGemini(userMessage, history, settings || {});
|
||||||
|
|
||||||
|
return NextResponse.json({ response: geminiResponse });
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Error with the model response " },
|
{error: "Error with the model response " + String(error)},
|
||||||
{ status: 500 }
|
{status : 500}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+124
-150
@@ -1,181 +1,157 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { motion, useMotionValue, useTransform } from "framer-motion";
|
import { motion, useMotionValue, useTransform, useAnimation } from "framer-motion";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { CardData } from "@/app/exportType/types";
|
||||||
|
|
||||||
// Removed local fallback JSX declaration — use the official React type definitions instead:
|
|
||||||
// install @types/react and @types/react-dom so JSX.IntrinsicElements is provided by those packages.
|
|
||||||
// 1. Updated ColorWay interface
|
|
||||||
interface ColorWay {
|
interface ColorWay {
|
||||||
card: string;
|
card: string;
|
||||||
text: string;
|
text: string;
|
||||||
titleColor: string; // Added title color
|
titleColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Updated COLOR_WAYS array
|
|
||||||
const COLOR_WAYS: ColorWay[] = [
|
const COLOR_WAYS: ColorWay[] = [
|
||||||
{ card: "#FEEEDF", text: "#173C46", titleColor: "#173C46" },
|
{ card: "#FEEEDF", text: "#173C46", titleColor: "#173C46" },
|
||||||
{ card: "#FEEEDF", text: "#EA627F", titleColor: "#173C46" }, // Example: Title is dark, text is pink
|
{ card: "#FEEEDF", text: "#EA627F", titleColor: "#173C46" },
|
||||||
{ card: "#DEE5D4", text: "#173C46", titleColor: "#EA627F" },
|
{ card: "#DEE5D4", text: "#173C46", titleColor: "#EA627F" },
|
||||||
{ card: "#DEE5D4", text: "#EA627F", titleColor: "#173C46" },
|
{ card: "#DEE5D4", text: "#EA627F", titleColor: "#173C46" },
|
||||||
{ card: "#B8C0AD", text: "#173C46", titleColor: "#173C46" },
|
{ card: "#B8C0AD", text: "#173C46", titleColor: "#173C46" },
|
||||||
{ card: "#B8C0AD", text: "#173C46", titleColor: "#EA627F" },
|
{ card: "#B8C0AD", text: "#173C46", titleColor: "#EA627F" },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface CardData {
|
const parseAiResponse = (markdownString: string): CardData[] => {
|
||||||
id: number;
|
const ideas: CardData[] = [];
|
||||||
title: string;
|
const ideaBlocks = markdownString.split("### ").filter(block => block.trim() !== "");
|
||||||
summary: string;
|
|
||||||
tracks: string[];
|
ideaBlocks.forEach((block, index) => {
|
||||||
|
const titleMatch = block.match(/^(.*?)\n/);
|
||||||
|
const conceptMatch = block.match(/\*\*Concept:\*\*\s*(.*)/s);
|
||||||
|
const tracksMatch = block.matchAll(/\*\*(?!Concept|Relevance|Core|Team|Bonus)(.*?):\*\*/g);
|
||||||
|
const tracks = [...tracksMatch].map(match => match[1].trim());
|
||||||
|
|
||||||
|
ideas.push({
|
||||||
|
id: index + 1,
|
||||||
|
title: titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`,
|
||||||
|
summary: conceptMatch ? conceptMatch[1].trim() : "No concept provided.",
|
||||||
|
tracks: tracks.length > 0 ? tracks : ["General"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return ideas;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CardsPage() {
|
||||||
|
const [cards, setCards] = useState<CardData[]>([]);
|
||||||
|
const [initialCards, setInitialCards] = useState<CardData[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const projectId = searchParams.get('projectId');
|
||||||
|
const projectTitle = searchParams.get('projectTitle');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const aiResponse = localStorage.getItem('temp_aiResponse');
|
||||||
|
if (aiResponse) {
|
||||||
|
localStorage.removeItem('temp_aiResponse');
|
||||||
|
const parsedCards = parseAiResponse(aiResponse);
|
||||||
|
setCards(parsedCards.reverse());
|
||||||
|
setInitialCards(parsedCards.reverse());
|
||||||
|
} else {
|
||||||
|
console.warn("No AI response found. Using hardcoded cards as fallback.");
|
||||||
|
const hardcodedCards: CardData[] = [
|
||||||
|
{ id: 1, title: "AI Project Idea 1", summary: "A cool project...", tracks: ["AI", "Web"] },
|
||||||
|
{ id: 2, title: "AI Project Idea 2", summary: "Another cool project...", tracks: ["Mobile", "Game"] },
|
||||||
|
];
|
||||||
|
setCards(hardcodedCards.reverse());
|
||||||
|
setInitialCards(hardcodedCards.reverse());
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div className="grid h-screen w-full place-items-center bg-orange-100"><p>Loading cards...</p></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!projectId || !projectTitle) {
|
||||||
|
return (
|
||||||
|
<div className="grid h-screen w-full place-items-center bg-orange-100">
|
||||||
|
<p>Missing project information. Please <a href="/formfile">start a new project</a>.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SwipeCards
|
||||||
|
initialCards={initialCards}
|
||||||
|
currentCards={cards}
|
||||||
|
setCards={setCards}
|
||||||
|
projectId={projectId}
|
||||||
|
projectTitle={projectTitle}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardData: CardData[] = [
|
interface SwipeCardsProps {
|
||||||
{
|
initialCards: CardData[];
|
||||||
|
currentCards: CardData[];
|
||||||
|
setCards: React.Dispatch<React.SetStateAction<CardData[]>>;
|
||||||
|
projectId: string;
|
||||||
|
projectTitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
id: 1,
|
const SwipeCards: React.FC<SwipeCardsProps> = ({
|
||||||
|
initialCards,
|
||||||
|
currentCards,
|
||||||
|
setCards,
|
||||||
|
projectId,
|
||||||
|
projectTitle
|
||||||
|
}) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
title: " WEBSITE",
|
const removeCard = () => {
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"FFind out what makes GoonMAXXER unique.With this innovative website you will be able to explore various features and functionalities that set GoonMAXXER apart from the rest.",
|
|
||||||
|
|
||||||
tracks: ["Gemini", "Auth0", "VibeCODING", "SmellyMaxxing", "shitposting"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 2,
|
|
||||||
|
|
||||||
title: "Intro to React",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Learn the fundamentals of React, including components, state, and props.",
|
|
||||||
|
|
||||||
tracks: ["React", "JS", "Frontend"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 3,
|
|
||||||
|
|
||||||
title: "State Management",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Explore complex state solutions with Zustand, Redux, and Context API.",
|
|
||||||
|
|
||||||
tracks: ["React", "Zustand", "State"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 4,
|
|
||||||
|
|
||||||
title: "Next.js 14",
|
|
||||||
|
|
||||||
summary: "Master the app router, server actions, and data fetching.",
|
|
||||||
|
|
||||||
tracks: ["Next.js", "React", "Fullstack"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 5,
|
|
||||||
|
|
||||||
title: "UI/UX Principles",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"A complete guide to user-centric design, prototyping, and testing.",
|
|
||||||
|
|
||||||
tracks: ["Design", "UI", "UX"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 6,
|
|
||||||
|
|
||||||
title: "Node.js Essentials",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Build scalable, high-performance backend applications with Node.js.",
|
|
||||||
|
|
||||||
tracks: ["Node", "Backend", "JS"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 7,
|
|
||||||
|
|
||||||
title: "TypeScript",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Add static typing to your JavaScript projects for robust, large-scale apps.",
|
|
||||||
|
|
||||||
tracks: ["TypeScript", "JS", "DevOps"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 8,
|
|
||||||
|
|
||||||
title: "Framer Motion",
|
|
||||||
|
|
||||||
summary: "Create production-ready animations for your React applications.",
|
|
||||||
|
|
||||||
tracks: ["React", "Animation", "Framer"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
].reverse();
|
|
||||||
|
|
||||||
const SwipeCards: React.FC = () => {
|
|
||||||
const [cards, setCards] = useState<CardData[]>(cardData);
|
|
||||||
|
|
||||||
const onSwipe = () => {
|
|
||||||
setCards((prev) => prev.slice(0, prev.length - 1));
|
setCards((prev) => prev.slice(0, prev.length - 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAccept = (cardData: CardData) => {
|
||||||
|
removeCard();
|
||||||
|
router.push(`/chatbot?projectId=${encodeURIComponent(projectId)}&projectTitle=${encodeURIComponent(projectTitle)}&cardTitle=${encodeURIComponent(cardData.title)}&cardSummary=${encodeURIComponent(cardData.summary)}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReject = () => {
|
||||||
|
removeCard();
|
||||||
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setCards(cardData);
|
setCards(initialCards);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="grid h-screen w-full place-items-center bg-orange-100"
|
className="grid h-screen w-full place-items-center bg-orange-100"
|
||||||
// -gradient-to-tr from-orange-200 to-orange-100
|
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'
|
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='0 0 32 32' width='32' height='32' fill='none'
|
viewBox='0 0 32 32' width='32' height='32' fill='none'
|
||||||
stroke-width='2' stroke='%23d4d4d4'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e")`,
|
stroke-width='2' stroke='%23d4d4d4'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e")`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{cards.length > 0 ? (
|
{currentCards.length > 0 ? (
|
||||||
cards.map((card, index) => {
|
currentCards.map((card, index) => {
|
||||||
const colorWay = COLOR_WAYS[card.id % COLOR_WAYS.length];
|
const colorWay = COLOR_WAYS[card.id % COLOR_WAYS.length];
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={card.id}
|
key={card.id}
|
||||||
cardData={card}
|
cardData={card}
|
||||||
colorWay={colorWay}
|
colorWay={colorWay}
|
||||||
isFront={index === cards.length - 1}
|
isFront={index === currentCards.length - 1}
|
||||||
onSwipe={onSwipe}
|
onAccept={handleAccept}
|
||||||
|
onReject={handleReject}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
// ... (empty state)
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-2xl font-bold text-neutral-700">
|
<h2 className="text-2xl font-bold text-neutral-700">
|
||||||
No more cards!
|
No more cards!
|
||||||
@@ -199,12 +175,13 @@ interface CardProps {
|
|||||||
cardData: CardData;
|
cardData: CardData;
|
||||||
colorWay: ColorWay;
|
colorWay: ColorWay;
|
||||||
isFront: boolean;
|
isFront: boolean;
|
||||||
onSwipe: () => void;
|
onAccept: (cardData: CardData) => void;
|
||||||
|
onReject: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) => {
|
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onReject }) => {
|
||||||
const { title, summary, tracks } = cardData;
|
const { title, summary, tracks } = cardData;
|
||||||
|
const controls = useAnimation();
|
||||||
const x = useMotionValue(0);
|
const x = useMotionValue(0);
|
||||||
const rotate = useTransform(x, [-250, 250], [-25, 25]);
|
const rotate = useTransform(x, [-250, 250], [-25, 25]);
|
||||||
const opacity = useTransform(
|
const opacity = useTransform(
|
||||||
@@ -217,9 +194,15 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
const rejectOpacity = useTransform(x, [-150, -50], [1, 0]);
|
const rejectOpacity = useTransform(x, [-150, -50], [1, 0]);
|
||||||
const badgeRotate = useTransform(x, [-150, 150], [-15, 15]);
|
const badgeRotate = useTransform(x, [-150, 150], [-15, 15]);
|
||||||
|
|
||||||
const handleDragEnd = (event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number } }) => {
|
const handleDragEnd = async (event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number } }) => {
|
||||||
if (Math.abs(info.offset.x) > 100) {
|
if (info.offset.x > 100) {
|
||||||
onSwipe();
|
await controls.start({ x: 1000, opacity: 0, transition: { duration: 0.1 } });
|
||||||
|
onAccept(cardData);
|
||||||
|
} else if (info.offset.x < -100) {
|
||||||
|
await controls.start({ x: -1000, opacity: 0, transition: { duration: 0.1 } });
|
||||||
|
onReject();
|
||||||
|
} else {
|
||||||
|
controls.start({ x: 0, rotate: 0, opacity: 1, transition: { type: "spring", stiffness: 300, damping: 30 } });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,7 +214,7 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
rotate,
|
rotate,
|
||||||
opacity,
|
opacity,
|
||||||
backgroundColor: colorWay.card,
|
backgroundColor: colorWay.card,
|
||||||
color: colorWay.text, // This is the default text color
|
color: colorWay.text,
|
||||||
zIndex: isFront ? 2 : 1,
|
zIndex: isFront ? 2 : 1,
|
||||||
pointerEvents: isFront ? "auto" : "none",
|
pointerEvents: isFront ? "auto" : "none",
|
||||||
}}
|
}}
|
||||||
@@ -244,7 +227,6 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
>
|
>
|
||||||
{/* Accept/Reject Badges */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
||||||
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
||||||
@@ -255,24 +237,20 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
||||||
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
||||||
>
|
>
|
||||||
<h3 className="text-5xl font-bold uppercase text-red-500">Reject</h3>
|
<h3 className="text-s5xl font-bold uppercase text-red-500">Reject</h3>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Card Content */}
|
|
||||||
<div className="relative z-0">
|
<div className="relative z-0">
|
||||||
{/* 3. Applied the specific titleColor here */}
|
|
||||||
<h2
|
<h2
|
||||||
className="mb-4 text-center text-7xl font-bold"
|
className="mb-4 text-center text-7xl font-bold"
|
||||||
style={{ color: colorWay.titleColor }}
|
style={{ color: colorWay.titleColor }}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{/* This summary <p> will inherit colorWay.text from the parent */}
|
|
||||||
<p className="text-center text-3xl font-light">{summary}</p>
|
<p className="text-center text-3xl font-light">{summary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
||||||
{/* This <h3> will also inherit colorWay.text */}
|
|
||||||
<h3 className="mb-4 w-full text-center text-4xl font-semibold">
|
<h3 className="mb-4 w-full text-center text-4xl font-semibold">
|
||||||
Tracks
|
Tracks
|
||||||
</h3>
|
</h3>
|
||||||
@@ -281,7 +259,6 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
key={track}
|
key={track}
|
||||||
className="flex items-center justify-center rounded-full px-8 py-3 text-2xl font-medium"
|
className="flex items-center justify-center rounded-full px-8 py-3 text-2xl font-medium"
|
||||||
style={{
|
style={{
|
||||||
// This logic now correctly contrasts with the main text color
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
colorWay.text === "#ffffff"
|
colorWay.text === "#ffffff"
|
||||||
? "rgba(255, 255, 255, 0.2)"
|
? "rgba(255, 255, 255, 0.2)"
|
||||||
@@ -294,7 +271,4 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SwipeCards;
|
|
||||||
+117
-80
@@ -1,26 +1,58 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef, useCallback } from "react";
|
import { useState, useEffect, useRef, useCallback, Suspense } from "react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams, useRouter } from "next/navigation";
|
||||||
import ChatInput from "../utils/component/ui/ChatInput";
|
import ChatInput from "../utils/component/ui/ChatInput";
|
||||||
import MessageWindow from "../utils/component/ui/MessageWindow";
|
import MessageWindow from "../utils/component/ui/MessageWindow";
|
||||||
import { ChatHistory, ChatSetting, Message, MessageRole } from "@/app/exportType/types";
|
import { ChatHistory, ChatSetting, Message, MessageRole } from "@/app/exportType/types";
|
||||||
|
import { getChatSessionByProjectId, saveChatSession } from "../utils/storage";
|
||||||
|
|
||||||
|
function getInitialChatState(
|
||||||
|
projectId: string | null,
|
||||||
|
): ChatHistory {
|
||||||
|
if (typeof window === 'undefined' || !projectId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const existingSession = getChatSessionByProjectId(projectId);
|
||||||
|
if (existingSession) {
|
||||||
|
return existingSession.history;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [history, setHistory] = useState<ChatHistory>([])
|
return (
|
||||||
const historyRef = useRef<ChatHistory>([]); // Create a ref for history
|
<Suspense fallback={<div>Loading chat...</div>}>
|
||||||
|
<ChatbotContent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChatbotContent() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const router = useRouter();
|
||||||
|
const projectId = searchParams.get('projectId');
|
||||||
|
const projectTitle = searchParams.get('projectTitle');
|
||||||
|
const cardTitle = searchParams.get('cardTitle');
|
||||||
|
const cardSummary = searchParams.get('cardSummary');
|
||||||
|
|
||||||
|
const [history, setHistory] = useState<ChatHistory>(() => getInitialChatState(projectId));
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
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-pro",
|
||||||
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"
|
||||||
|
|
||||||
});
|
});
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const initialMessageSent = useRef(false);
|
const initialMessageSent = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
historyRef.current = history; // Keep the ref updated with the latest history
|
historyRef.current = history;
|
||||||
}, [history]);
|
if (history.length > 0 && projectId && projectTitle) {
|
||||||
|
saveChatSession(projectId, projectTitle, history);
|
||||||
|
}
|
||||||
|
}, [history, projectId, projectTitle]);
|
||||||
|
|
||||||
const handleSend = useCallback(async (message: string) => {
|
const handleSend = useCallback(async (message: string) => {
|
||||||
const newUserMessage: Message = {
|
const newUserMessage: Message = {
|
||||||
@@ -28,27 +60,84 @@ export default function Home() {
|
|||||||
parts: [{ text: message }],
|
parts: [{ text: message }],
|
||||||
};
|
};
|
||||||
|
|
||||||
setHistory(prevHistory => {
|
const currentHistory = [...historyRef.current, newUserMessage];
|
||||||
const currentHistory = [...prevHistory, newUserMessage];
|
setHistory(currentHistory);
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
const sendApiRequest = async () => {
|
try {
|
||||||
|
const response = await fetch("/api/chat", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
userMessage: message,
|
||||||
|
history: historyRef.current,
|
||||||
|
settings: settings,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
console.error("AI Error:", data.error);
|
||||||
|
const aiErrorMessage: Message = {
|
||||||
|
role: "model",
|
||||||
|
parts: [{ text: `Error: ${data.error}` }],
|
||||||
|
};
|
||||||
|
setHistory(prevHistory => [...prevHistory, aiErrorMessage]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aiMessage: Message = {
|
||||||
|
role: "model" as MessageRole,
|
||||||
|
parts: [{ text: data.response }],
|
||||||
|
};
|
||||||
|
|
||||||
|
setHistory(prevHistory => [...prevHistory, aiMessage]);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Request Failed:", error);
|
||||||
|
setLoading(false);
|
||||||
|
const aiErrorMessage: Message = {
|
||||||
|
role: "model",
|
||||||
|
parts: [{ text: `Error: Could not connect to AI service.` }],
|
||||||
|
};
|
||||||
|
setHistory(prevHistory => [...prevHistory, aiErrorMessage]);
|
||||||
|
}
|
||||||
|
}, [settings, projectId, projectTitle]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cardTitle && cardSummary && history.length === 0 && !initialMessageSent.current) {
|
||||||
|
initialMessageSent.current = true;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const message = `Explain the project titled '${cardTitle}' with the summary '${cardSummary}' in detail, and suggest suitable roles for a team working on it.`;
|
||||||
|
|
||||||
|
const newUserMessage: Message = {
|
||||||
|
role: "user",
|
||||||
|
parts: [{ text: message }],
|
||||||
|
};
|
||||||
|
|
||||||
|
setHistory([newUserMessage]);
|
||||||
|
|
||||||
|
const sendInitialApiRequest = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/chat", {
|
const response = await fetch("/api/chat", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userMessage: message,
|
userMessage: message,
|
||||||
history: historyRef.current, // Use the ref for history
|
history: [],
|
||||||
settings: settings,
|
settings: settings,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.error("AI Error:", data.error);
|
console.error("AI Error:", data.error);
|
||||||
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,78 +146,26 @@ export default function Home() {
|
|||||||
parts: [{ text: data.response }],
|
parts: [{ text: data.response }],
|
||||||
};
|
};
|
||||||
|
|
||||||
setHistory(prevHistory => [...prevHistory, aiMessage]);
|
setHistory((prevHistory) => [...prevHistory, aiMessage]);
|
||||||
|
} catch (error) {
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error("Request Failed:", error);
|
console.error("Request Failed:", error);
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sendApiRequest();
|
|
||||||
|
|
||||||
return currentHistory;
|
sendInitialApiRequest();
|
||||||
});
|
|
||||||
}, [settings]); // Now handleSend only depends on settings
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!initialMessageSent.current) {
|
|
||||||
const title = searchParams.get('title');
|
|
||||||
const summary = searchParams.get('summary');
|
|
||||||
|
|
||||||
if (title && summary) {
|
|
||||||
const message = `Explain the project titled '${title}' with the summary '${summary}' in detail, and suggest suitable roles for a team working on it.`;
|
|
||||||
|
|
||||||
const newUserMessage: Message = {
|
|
||||||
role: "user",
|
|
||||||
parts: [{ text: message }],
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setHistory((prevHistory) => [...prevHistory, newUserMessage]); // Add user message to history
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
const sendInitialApiRequest = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/chat", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
userMessage: message,
|
|
||||||
history: [...historyRef.current, newUserMessage], // Use ref for history
|
|
||||||
settings: settings,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.error) {
|
|
||||||
console.error("AI Error:", data.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const aiMessage: Message = {
|
|
||||||
role: "model" as MessageRole,
|
|
||||||
parts: [{ text: data.response }],
|
|
||||||
};
|
|
||||||
|
|
||||||
setHistory((prevHistory) => [...prevHistory, aiMessage]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Request Failed:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sendInitialApiRequest();
|
|
||||||
initialMessageSent.current = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [searchParams, settings]); // Dependencies for this useEffect
|
}, [cardTitle, cardSummary, history.length, settings, projectId, projectTitle]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col py-32">
|
<div className="flex flex-col py-32">
|
||||||
<MessageWindow history={history} />
|
<button
|
||||||
|
onClick={() => router.push('/Dashboard')}
|
||||||
|
className="absolute top-4 right-4 bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
|
||||||
|
>
|
||||||
|
Exit
|
||||||
|
</button>
|
||||||
|
<MessageWindow history={history} isLoading={loading} />
|
||||||
<ChatInput onSend={handleSend} onOpenSettings={() => { }} />
|
<ChatInput onSend={handleSend} onOpenSettings={() => { }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+54
-121
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { motion, useMotionValue, useTransform } from "framer-motion";
|
import { motion, useMotionValue, useTransform } from "framer-motion";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
// Removed local fallback JSX declaration — use the official React type definitions instead:
|
// Removed local fallback JSX declaration — use the official React type definitions instead:
|
||||||
// install @types/react and @types/react-dom so JSX.IntrinsicElements is provided by those packages.
|
// install @types/react and @types/react-dom so JSX.IntrinsicElements is provided by those packages.
|
||||||
@@ -29,126 +30,49 @@ interface CardData {
|
|||||||
tracks: string[];
|
tracks: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardData: CardData[] = [
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 1,
|
|
||||||
|
|
||||||
title: " WEBSITE",
|
interface SwipeCardsProps {
|
||||||
|
initialCards: CardData[];
|
||||||
|
}
|
||||||
|
|
||||||
summary:
|
const SwipeCards: React.FC<SwipeCardsProps> = ({ initialCards }) => {
|
||||||
|
const [cards, setCards] = useState<CardData[]>(initialCards);
|
||||||
"FFind out what makes GoonMAXXER unique.With this innovative website you will be able to explore various features and functionalities that set GoonMAXXER apart from the rest.",
|
const router = useRouter();
|
||||||
|
|
||||||
tracks: ["Gemini", "Auth0", "VibeCODING", "SmellyMaxxing", "shitposting"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 2,
|
|
||||||
|
|
||||||
title: "Intro to React",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Learn the fundamentals of React, including components, state, and props.",
|
|
||||||
|
|
||||||
tracks: ["React", "JS", "Frontend"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 3,
|
|
||||||
|
|
||||||
title: "State Management",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Explore complex state solutions with Zustand, Redux, and Context API.",
|
|
||||||
|
|
||||||
tracks: ["React", "Zustand", "State"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 4,
|
|
||||||
|
|
||||||
title: "Next.js 14",
|
|
||||||
|
|
||||||
summary: "Master the app router, server actions, and data fetching.",
|
|
||||||
|
|
||||||
tracks: ["Next.js", "React", "Fullstack"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 5,
|
|
||||||
|
|
||||||
title: "UI/UX Principles",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"A complete guide to user-centric design, prototyping, and testing.",
|
|
||||||
|
|
||||||
tracks: ["Design", "UI", "UX"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 6,
|
|
||||||
|
|
||||||
title: "Node.js Essentials",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Build scalable, high-performance backend applications with Node.js.",
|
|
||||||
|
|
||||||
tracks: ["Node", "Backend", "JS"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 7,
|
|
||||||
|
|
||||||
title: "TypeScript",
|
|
||||||
|
|
||||||
summary:
|
|
||||||
|
|
||||||
"Add static typing to your JavaScript projects for robust, large-scale apps.",
|
|
||||||
|
|
||||||
tracks: ["TypeScript", "JS", "DevOps"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 8,
|
|
||||||
|
|
||||||
title: "Framer Motion",
|
|
||||||
|
|
||||||
summary: "Create production-ready animations for your React applications.",
|
|
||||||
|
|
||||||
tracks: ["React", "Animation", "Framer"],
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
].reverse();
|
|
||||||
|
|
||||||
const SwipeCards: React.FC = () => {
|
|
||||||
const [cards, setCards] = useState<CardData[]>(cardData);
|
|
||||||
|
|
||||||
const onSwipe = () => {
|
const onSwipe = () => {
|
||||||
setCards((prev) => prev.slice(0, prev.length - 1));
|
setCards((prev) => prev.slice(0, prev.length - 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRightSwipe = async (card: CardData) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/chat", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
userMessage: `Explain the project: ${card.title}. Summary: ${card.summary}. Tracks: ${card.tracks.join(", ")}`,
|
||||||
|
history: [], // Provide an empty array for history
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorBody = await response.text(); // Read the response body
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming the chat API handles the message and we can then navigate
|
||||||
|
router.push(`/chatbot?title=${encodeURIComponent(card.title)}&summary=${encodeURIComponent(card.summary)}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending project to chatbot:", error);
|
||||||
|
// Optionally, handle error state or show a message to the user
|
||||||
|
}
|
||||||
|
onSwipe(); // Remove the card after swiping right
|
||||||
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setCards(cardData);
|
setCards(initialCards);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -171,6 +95,7 @@ const SwipeCards: React.FC = () => {
|
|||||||
colorWay={colorWay}
|
colorWay={colorWay}
|
||||||
isFront={index === cards.length - 1}
|
isFront={index === cards.length - 1}
|
||||||
onSwipe={onSwipe}
|
onSwipe={onSwipe}
|
||||||
|
onRightSwipe={handleRightSwipe}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -199,10 +124,11 @@ interface CardProps {
|
|||||||
cardData: CardData;
|
cardData: CardData;
|
||||||
colorWay: ColorWay;
|
colorWay: ColorWay;
|
||||||
isFront: boolean;
|
isFront: boolean;
|
||||||
onSwipe: () => void;
|
onSwipe: () => void; // For left swipe (reject)
|
||||||
|
onRightSwipe: (card: CardData) => void; // For right swipe (accept)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) => {
|
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe, onRightSwipe }) => {
|
||||||
const { title, summary, tracks } = cardData;
|
const { title, summary, tracks } = cardData;
|
||||||
|
|
||||||
const x = useMotionValue(0);
|
const x = useMotionValue(0);
|
||||||
@@ -218,14 +144,18 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
const badgeRotate = useTransform(x, [-150, 150], [-15, 15]);
|
const badgeRotate = useTransform(x, [-150, 150], [-15, 15]);
|
||||||
|
|
||||||
const handleDragEnd = (event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number } }) => {
|
const handleDragEnd = (event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number } }) => {
|
||||||
if (Math.abs(info.offset.x) > 100) {
|
if (info.offset.x > 100) {
|
||||||
|
// Swiped right
|
||||||
|
onRightSwipe(cardData);
|
||||||
|
} else if (info.offset.x < -100) {
|
||||||
|
// Swiped left
|
||||||
onSwipe();
|
onSwipe();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute flex h-[48rem] w-[36rem] flex-col justify-between overflow-hidden rounded-2xl p-6 shadow-2xl hover:cursor-grab active:cursor-grabbing"
|
className="absolute flex h-[40rem] w-[30rem] flex-col justify-between overflow-hidden rounded-2xl p-6 shadow-2xl hover:cursor-grab active:cursor-grabbing"
|
||||||
style={{
|
style={{
|
||||||
x,
|
x,
|
||||||
rotate,
|
rotate,
|
||||||
@@ -243,43 +173,46 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
drag={isFront ? "x" : false}
|
drag={isFront ? "x" : false}
|
||||||
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
|
initial={{ x: 500, opacity: 0 }} // Start off-screen to the right and invisible
|
||||||
|
animate={{ x: 0, opacity: 1 }} // Animate to its natural position and fully visible
|
||||||
|
transition={{ type: "spring", stiffness: 100, damping: 20 }} // Smooth spring animation
|
||||||
>
|
>
|
||||||
{/* Accept/Reject Badges */}
|
{/* Accept/Reject Badges */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
||||||
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
||||||
>
|
>
|
||||||
<h3 className="text-5xl font-bold uppercase text-green-500">Accept</h3>
|
<h3 className="text-2xl font-bold uppercase text-green-500">Accept</h3>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
||||||
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
||||||
>
|
>
|
||||||
<h3 className="text-5xl font-bold uppercase text-red-500">Reject</h3>
|
<h3 className="text-2xl font-bold uppercase text-red-500">Reject</h3>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Card Content */}
|
{/* Card Content */}
|
||||||
<div className="relative z-0">
|
<div className="relative z-0">
|
||||||
{/* 3. Applied the specific titleColor here */}
|
{/* 3. Applied the specific titleColor here */}
|
||||||
<h2
|
<h2
|
||||||
className="mb-4 text-center text-7xl font-bold"
|
className="mb-4 text-center text-2xl font-bold"
|
||||||
style={{ color: colorWay.titleColor }}
|
style={{ color: colorWay.titleColor }}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{/* This summary <p> will inherit colorWay.text from the parent */}
|
{/* This summary <p> will inherit colorWay.text from the parent */}
|
||||||
<p className="text-center text-3xl font-light">{summary}</p>
|
<p className="text-center text-sm font-light">{summary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
||||||
{/* This <h3> will also inherit colorWay.text */}
|
{/* This <h3> will also inherit colorWay.text */}
|
||||||
<h3 className="mb-4 w-full text-center text-4xl font-semibold">
|
<h3 className="mb-4 w-full text-center text-md font-semibold">
|
||||||
Tracks
|
Tracks
|
||||||
</h3>
|
</h3>
|
||||||
{tracks.map((track) => (
|
{tracks.map((track) => (
|
||||||
<span
|
<span
|
||||||
key={track}
|
key={track}
|
||||||
className="flex items-center justify-center rounded-full px-8 py-3 text-2xl font-medium"
|
className="flex items-center justify-center rounded-full px-2 py-1 text-xs font-medium"
|
||||||
style={{
|
style={{
|
||||||
// This logic now correctly contrasts with the main text color
|
// This logic now correctly contrasts with the main text color
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface GenerateConfig {
|
|||||||
export interface ChatSetting {
|
export interface ChatSetting {
|
||||||
temperature: number;
|
temperature: number;
|
||||||
model: string;
|
model: string;
|
||||||
sysTemInstructions: string;
|
systemInstructions: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardData {
|
export interface CardData {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ export default async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Destructure the data sent from the frontend
|
// Destructure the data sent from the frontend
|
||||||
let { prompt, settings } = req.body;
|
let prompt = req.body.prompt;
|
||||||
|
const settings = req.body.settings;
|
||||||
|
|
||||||
// Basic validation
|
// Basic validation
|
||||||
if (!prompt || typeof prompt !== 'string') {
|
if (!prompt || typeof prompt !== 'string') {
|
||||||
|
|||||||
+90
-57
@@ -1,10 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback, useRef } 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: '',
|
||||||
@@ -15,7 +18,6 @@ const getDefaultFormData = (): FormData => ({
|
|||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- New Initializer Function ---
|
|
||||||
const getInitialState = () => {
|
const getInitialState = () => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return {
|
return {
|
||||||
@@ -29,8 +31,8 @@ const getInitialState = () => {
|
|||||||
localStorage.removeItem('formData');
|
localStorage.removeItem('formData');
|
||||||
return {
|
return {
|
||||||
data: JSON.parse(storedFormData) as FormData,
|
data: JSON.parse(storedFormData) as FormData,
|
||||||
showForm: false,
|
showForm: true,
|
||||||
runEffect: true,
|
runEffect: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -40,41 +42,53 @@ const getInitialState = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
|
||||||
**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.]`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------------------------------------
|
|
||||||
|
|
||||||
export default function Home() {
|
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 [initialState] = useState(getInitialState);
|
||||||
const [showForm, setShowForm] = useState(initialState.showForm);
|
const [showForm, setShowForm] = useState(initialState.showForm);
|
||||||
const [formData, setFormData] = useState<FormData>(initialState.data);
|
const initialIdeaGenerationAttempted = useRef(false);
|
||||||
const [ideaResponse, setIdeaResponse] = useState<string>("");
|
|
||||||
|
|
||||||
// The 'settings' state [useState] is now removed from here.
|
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:
|
||||||
|
### Project Idea Title
|
||||||
|
**Track** [print out the track you used]
|
||||||
|
**Concept:** [A concise, single paragraph description of the product and its primary function.]`,
|
||||||
|
});
|
||||||
|
|
||||||
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) => {
|
||||||
// 1. Data Cleaning
|
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 = {
|
const submissionData: FormData = {
|
||||||
...dataToProcess,
|
...dataToProcess,
|
||||||
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
|
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
|
||||||
@@ -86,7 +100,6 @@ export default function Home() {
|
|||||||
.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}
|
||||||
@@ -97,20 +110,22 @@ export default function Home() {
|
|||||||
|
|
||||||
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", {
|
const response = await fetch("/api/generate-idea", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt: prompt,
|
prompt: prompt,
|
||||||
settings: AI_SETTINGS, // Use the constant
|
settings: settings,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.error || !response.ok) {
|
if (data.error || !response.ok) {
|
||||||
console.error("AI Error:", data.error);
|
console.error("AI Error:", data.error);
|
||||||
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
|
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
|
||||||
@@ -118,7 +133,12 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
setIdeaResponse(data.response);
|
setIdeaResponse(data.response);
|
||||||
|
|
||||||
// FIX 2 (Unexpected any): Changed 'any' to 'unknown' and added a type check.
|
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) {
|
} 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.";
|
||||||
@@ -127,18 +147,20 @@ 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(() => {
|
||||||
if (initialState.runEffect) {
|
if (initialState.runEffect) {
|
||||||
generateIdeas(initialState.data);
|
//generateIdeas(initialState.data, "some-resumed-id");
|
||||||
}
|
}
|
||||||
}, [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 bg-gray-50 flex items-start justify-center p-4 sm:p-6 font-inter">
|
||||||
@@ -147,22 +169,33 @@ export default function Home() {
|
|||||||
<FormPage onFormSubmit={handleFormSubmitFromFormPage} />
|
<FormPage onFormSubmit={handleFormSubmitFromFormPage} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight">
|
{generatedCards.length > 0 ? (
|
||||||
Generated Project Idea 💡
|
<SwipeCards initialCards={generatedCards} />
|
||||||
</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>
|
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight">
|
||||||
<div
|
Generated Project Idea 💡
|
||||||
className="prose max-w-none"
|
</h1>
|
||||||
dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }}
|
<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>
|
||||||
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
|
<div
|
||||||
</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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowForm(true)}
|
onClick={() => {
|
||||||
|
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"
|
||||||
>
|
>
|
||||||
Go Back to Form
|
Create New Project
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+11
-15
@@ -1,3 +1,5 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
/* @import "tailwindcss";
|
/* @import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -26,7 +28,6 @@ body {
|
|||||||
} */
|
} */
|
||||||
/* 1. Import BOTH fonts in one go */
|
/* 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";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -40,12 +41,15 @@ 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);
|
||||||
|
|
||||||
/* We add a new font family for headers (Pixelify Sans) */
|
|
||||||
/* This also creates a 'font-header' utility class */
|
|
||||||
--font-header: var(--font-pixelify);
|
--font-header: var(--font-pixelify);
|
||||||
|
>>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105
|
||||||
|
|
||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-geist-mono);
|
||||||
}
|
}
|
||||||
@@ -60,17 +64,9 @@ body {
|
|||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
|
|
||||||
/* 4. Body uses --font-sans (Concert One) by default */
|
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
|
h1,h2,h3,h4,h5,h6 {
|
||||||
/* 5. (Optional but helpful) Apply header font to h-tags by default */
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-family: var(--font-header);
|
font-family: var(--font-header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-11
@@ -17,14 +17,13 @@ export default function Home() {
|
|||||||
height={20}
|
height={20}
|
||||||
// "priority" is not a valid <img> attribute, so it's removed
|
// "priority" is not a valid <img> attribute, so it's removed
|
||||||
/>
|
/>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-6">
|
<Link href="/Dashboard" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
||||||
<a href="/dashboard" className="hover:text-blue-600 transition-colors">
|
|
||||||
Dashboard
|
Dashboard
|
||||||
</a>
|
</Link>
|
||||||
<a href="/cards" className="hover:text-blue-600 transition-colors">
|
<Link href="/cards" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
||||||
Cards
|
Cards
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -45,14 +44,13 @@ 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>
|
||||||
|
|
||||||
{/* Replaced <button><Link...></button> with a single <a> tag styled as a button */}
|
<Link
|
||||||
<a
|
href="/Dashboard"
|
||||||
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"
|
||||||
>
|
>
|
||||||
Get Started
|
<Link href="/dashboard">Get Started</Link>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 flex justify-center">
|
<div className="flex-1 flex justify-center">
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import { ChatHistory } from "@/app/exportType/types";
|
|||||||
import { User, Bot } from "lucide-react"
|
import { User, Bot } from "lucide-react"
|
||||||
|
|
||||||
interface MessageWindowProps {
|
interface MessageWindowProps {
|
||||||
history: ChatHistory
|
history: ChatHistory;
|
||||||
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MessageWindow({ history }: MessageWindowProps) {
|
export default function MessageWindow({ history, isLoading }: MessageWindowProps) {
|
||||||
const messageEndRef = useRef<HTMLDivElement>(null);
|
const messageEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, [history]);
|
}, [history, isLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 p-3 overflow-y-auto">
|
<div className="flex-1 p-3 overflow-y-auto">
|
||||||
@@ -49,12 +50,41 @@ export default function MessageWindow({ history }: MessageWindowProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{isLoading && (
|
||||||
{/* Invisible element to help scroll to bottom */}
|
<div className="flex justify-start mb-4">
|
||||||
<div ref={messageEndRef} />
|
<div className="mr-2">
|
||||||
</div>
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200">
|
||||||
|
<Bot size={15} className="text-gray-700" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 py-2 shadow-sm rounded-lg bg-white text-gray-800 rounded-bl-none max-w-xs sm:max-w-md">
|
||||||
|
<div className="whitespace-pre-wrap break-words">
|
||||||
|
{/* Simple loading dots animation */}
|
||||||
|
<span className="animate-pulse">...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="mr-2">
|
||||||
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray">
|
||||||
|
<Bot size={15} className="text-gray-700" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 py-2 shadow-sm rounded-lg bg-white text-gray-800 rounded-bl-none max-w-xs sm:max-w-md">
|
||||||
|
<div className="whitespace-pre-wrap break-words">
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Invisible element to help scroll to bottom */}
|
||||||
|
<div ref={messageEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+22
-18
@@ -10,21 +10,21 @@ if (!apiKey) {
|
|||||||
const genAI = new GoogleGenerativeAI(apiKey);
|
const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
export async function chatToGemini(
|
export async function chatToGemini(
|
||||||
userMessage: string,
|
userMessage:string ,
|
||||||
history: ChatHistory,
|
history:ChatHistory,
|
||||||
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",
|
||||||
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 summery first before going to detail",
|
||||||
});
|
});
|
||||||
|
|
||||||
const generationConfig: GenerateConfig = {
|
const generationConfig:GenerateConfig = {
|
||||||
temperature: settings.temperature || 1,
|
temperature : settings.temperature || 1,
|
||||||
topP: 0.9,
|
topP : 0.9,
|
||||||
responseMimeType: "text/plain",
|
responseMimeType: "text/plain",
|
||||||
maxOutputTokens: 1900, // Increased to allow for longer responses
|
maxOutputTokens: 1900, // Increased to allow for longer responses
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatSession = model.startChat({
|
const chatSession = model.startChat({
|
||||||
generationConfig,
|
generationConfig,
|
||||||
@@ -35,8 +35,12 @@ export async function chatToGemini(
|
|||||||
const result = await chatSession.sendMessage(userMessage);
|
const result = await chatSession.sendMessage(userMessage);
|
||||||
return result.response.text();
|
return result.response.text();
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error: unknown) {
|
||||||
console.error(error)
|
if (error instanceof Error) {
|
||||||
throw error
|
console.error("Error in chatToGemini:", error.name, error.message, error);
|
||||||
|
} else {
|
||||||
|
console.error("Unknown error in chatToGemini:", error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { FormData } from "@/app/formfile/formTypes";
|
||||||
|
import { ChatHistory } from "@/app/exportType/types";
|
||||||
|
|
||||||
|
// 1. Define the data structures for storage
|
||||||
|
export interface StoredProject extends FormData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoredChatSession {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
projectTitle: string;
|
||||||
|
history: ChatHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserData {
|
||||||
|
projects: StoredProject[];
|
||||||
|
chatSessions: StoredChatSession[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const STORAGE_KEY = "hackSparkUserData";
|
||||||
|
|
||||||
|
function safeGetLocalStorage(): UserData {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return { projects: [], chatSessions: [] };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return data ? JSON.parse(data) : { projects: [], chatSessions: [] };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to parse localStorage data:", error);
|
||||||
|
return { projects: [], chatSessions: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Retrieves all stored projects.
|
||||||
|
export function getStoredProjects(): StoredProject[] {
|
||||||
|
return safeGetLocalStorage().projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Retrieves all stored chat sessions.
|
||||||
|
export function getStoredChatSessions(): StoredChatSession[] {
|
||||||
|
return safeGetLocalStorage().chatSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Finds a specific chat session by its project ID.
|
||||||
|
export function getChatSessionByProjectId(projectId: string): StoredChatSession | undefined {
|
||||||
|
if (typeof window === 'undefined') return undefined;
|
||||||
|
const data = safeGetLocalStorage();
|
||||||
|
return data.chatSessions.find(session => session.projectId === projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveProject(formData: FormData): StoredProject {
|
||||||
|
if (typeof window === 'undefined') throw new Error("localStorage is not available.");
|
||||||
|
|
||||||
|
const data = safeGetLocalStorage();
|
||||||
|
const newProject: StoredProject = {
|
||||||
|
...formData,
|
||||||
|
id: new Date().toISOString(),
|
||||||
|
title: formData.Hackathon || "Untitled Project",
|
||||||
|
};
|
||||||
|
|
||||||
|
data.projects.push(newProject);
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||||||
|
return newProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveChatSession(projectId: string, projectTitle: string, history: ChatHistory) {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const data = safeGetLocalStorage();
|
||||||
|
const existingSessionIndex = data.chatSessions.findIndex(session => session.projectId === projectId);
|
||||||
|
|
||||||
|
if (existingSessionIndex > -1) {
|
||||||
|
data.chatSessions[existingSessionIndex].history = history;
|
||||||
|
} else {
|
||||||
|
const newSession: StoredChatSession = {
|
||||||
|
id: new Date().toISOString(),
|
||||||
|
projectId,
|
||||||
|
projectTitle,
|
||||||
|
history,
|
||||||
|
};
|
||||||
|
data.chatSessions.push(newSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setProjectForEditing(project: StoredProject) {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
localStorage.setItem('formData', JSON.stringify(project));
|
||||||
|
}
|
||||||
Generated
+12
@@ -66,6 +66,7 @@
|
|||||||
"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",
|
||||||
@@ -1470,6 +1471,7 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
@@ -1526,6 +1528,7 @@
|
|||||||
"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",
|
||||||
@@ -2023,6 +2026,7 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@@ -2341,6 +2345,7 @@
|
|||||||
"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",
|
||||||
@@ -2870,6 +2875,7 @@
|
|||||||
"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",
|
||||||
@@ -3047,6 +3053,7 @@
|
|||||||
"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",
|
||||||
@@ -5123,6 +5130,7 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
@@ -5131,6 +5139,7 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@@ -5776,6 +5785,7 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@@ -5927,6 +5937,7 @@
|
|||||||
"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"
|
||||||
@@ -6187,6 +6198,7 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user