This commit is contained in:
bismay70
2025-10-19 17:57:46 +05:30
parent cdcbfc8b90
commit 004f3232a8
10 changed files with 708 additions and 13 deletions

210
package-lock.json generated
View File

@@ -12,9 +12,11 @@
"@react-three/fiber": "^9.3.0",
"@react-three/postprocessing": "^3.0.4",
"framer-motion": "^12.23.24",
"mongoose": "^8.19.1",
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-icons": "^5.5.0",
"react-typed": "^2.0.12"
},
"devDependencies": {
@@ -783,6 +785,15 @@
"integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
"license": "Apache-2.0"
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
"integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
}
},
"node_modules/@monogrid/gainmap-js": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.1.0.tgz",
@@ -1530,12 +1541,27 @@
"meshoptimizer": "~0.22.0"
}
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
"license": "MIT"
},
"node_modules/@types/webxr": {
"version": "0.5.24",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
"license": "MIT"
},
"node_modules/@types/whatwg-url": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
"license": "MIT",
"dependencies": {
"@types/webidl-conversions": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
@@ -2463,6 +2489,15 @@
"node": ">=8"
}
},
"node_modules/bson": {
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
"license": "Apache-2.0",
"engines": {
"node": ">=16.20.1"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -2743,7 +2778,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -4579,6 +4613,15 @@
"node": ">=4.0"
}
},
"node_modules/kareem": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
"license": "Apache-2.0",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4937,6 +4980,12 @@
"node": ">= 0.4"
}
},
"node_modules/memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"license": "MIT"
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -5022,6 +5071,84 @@
"node": ">= 18"
}
},
"node_modules/mongodb": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
"integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.3.0",
"bson": "^6.10.4",
"mongodb-connection-string-url": "^3.0.2"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.3.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
"license": "Apache-2.0",
"dependencies": {
"@types/whatwg-url": "^11.0.2",
"whatwg-url": "^14.1.0 || ^13.0.0"
}
},
"node_modules/mongoose": {
"version": "8.19.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.19.1.tgz",
"integrity": "sha512-oB7hGQJn4f8aebqE7mhE54EReb5cxVgpCxQCQj0K/cK3q4J3Tg08nFP6sM52nJ4Hlm8jsDnhVYpqIITZUAhckQ==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
"mongodb": "~6.20.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
@@ -5037,11 +5164,31 @@
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mquery": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
"license": "MIT",
"dependencies": {
"debug": "4.x"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/n8ao": {
@@ -5514,7 +5661,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -5562,6 +5708,15 @@
"react": "^19.1.0"
}
},
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -6009,6 +6164,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sift": {
"version": "17.1.3",
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -6018,6 +6179,15 @@
"node": ">=0.10.0"
}
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"license": "MIT",
"dependencies": {
"memory-pager": "^1.0.2"
}
},
"node_modules/stable-hash": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -6397,6 +6567,18 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/troika-three-text": {
"version": "0.52.4",
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
@@ -6707,6 +6889,28 @@
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
"license": "MIT"
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -13,9 +13,11 @@
"@react-three/fiber": "^9.3.0",
"@react-three/postprocessing": "^3.0.4",
"framer-motion": "^12.23.24",
"mongoose": "^8.19.1",
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-icons": "^5.5.0",
"react-typed": "^2.0.12"
},
"devDependencies": {

BIN
public/ai.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

BIN
public/clg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

View File

@@ -0,0 +1,41 @@
import connectToDatabase from "../../../lib/db";
import Project from "../../../models/Projects";
// 🟢 Create new project
export async function POST(req) {
try {
const { name, techStack, description, githubLink, deployedLink } = await req.json();
await connectToDatabase();
const newProject = new Project({
name,
techStack,
description,
githubLink,
deployedLink,
});
await newProject.save();
return new Response(
JSON.stringify({ message: "Project added successfully!" }),
{ status: 201 }
);
} catch (error) {
console.error(" Error saving project:", error);
return new Response(JSON.stringify({ error: error.message }), { status: 500 });
}
}
// 🔵 Fetch all projects
export async function GET() {
try {
await connectToDatabase();
const projects = await Project.find({});
return new Response(JSON.stringify(projects), { status: 200 });
} catch (error) {
console.error(" Error fetching projects:", error);
return new Response(JSON.stringify({ error: error.message }), { status: 500 });
}
}

View File

@@ -29,7 +29,8 @@ const ANIMATIONS = {
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false);
const [showDropdown, setShowDropdown] = useState(false);
const [showDropdownResources, setShowDropdownResources] = useState(false);
const [showDropdownProjects, setShowDropdownProjects] = useState(false);
useEffect(() => {
const onKey = (e) => e.key === "Escape" && setIsOpen(false);
@@ -46,8 +47,8 @@ export default function Navbar() {
const mainLinks = [
"Home",
"Events",
"Projects",
"Events",
"Team",
"Resources",
"Contact",
@@ -119,7 +120,6 @@ export default function Navbar() {
variants={ANIMATIONS.menuItem}
className="flex flex-col items-start w-full"
>
{/* Title */}
<motion.span
className="text-xl text-white font-semibold mb-2"
variants={ANIMATIONS.menuItem}
@@ -127,7 +127,6 @@ export default function Navbar() {
Resources
</motion.span>
{/* Subsections with vertical blue line */}
<motion.div
className="flex flex-col items-start gap-2 border-l-2 border-blue-500 pl-4"
variants={ANIMATIONS.menuItem}
@@ -136,6 +135,7 @@ export default function Navbar() {
href="/onlineresources"
className="text-white text-base hover:text-blue-300"
variants={ANIMATIONS.menuItem}
onClick={() => setIsOpen(false)}
>
Online Resources
</motion.a>
@@ -143,6 +143,7 @@ export default function Navbar() {
href="/books"
className="text-white text-base hover:text-blue-300"
variants={ANIMATIONS.menuItem}
onClick={() => setIsOpen(false)}
>
Books
</motion.a>
@@ -151,6 +152,45 @@ export default function Navbar() {
);
}
if (item === "Projects") {
return (
<motion.div
key="projects"
variants={ANIMATIONS.menuItem}
className="flex flex-col items-start w-full"
>
<motion.span
className="text-xl text-white font-semibold mb-2"
variants={ANIMATIONS.menuItem}
>
Projects
</motion.span>
<motion.div
className="flex flex-col items-start gap-2 border-l-2 border-blue-500 pl-4"
variants={ANIMATIONS.menuItem}
>
<motion.a
href="/upload"
className="text-white text-base hover:text-blue-300"
variants={ANIMATIONS.menuItem}
onClick={() => setIsOpen(false)}
>
Upload Projects
</motion.a>
<motion.a
href="/projects"
className="text-white text-base hover:text-blue-300"
variants={ANIMATIONS.menuItem}
onClick={() => setIsOpen(false)}
>
View Projects
</motion.a>
</motion.div>
</motion.div>
);
}
return (
<motion.a
key={item}
@@ -228,27 +268,71 @@ export default function Navbar() {
</button>
<motion.div
className="flex flex-col md:flex-row md:flex-wrap items-center justify-center gap-6 max-w-4xl px-4"
className="flex flex-wrap items-center justify-center gap-6 max-w-4xl px-4"
variants={ANIMATIONS.menuContainer}
>
{mainLinks.map((item) => {
if (item === "Projects") {
return (
<div
key="projects"
className="relative group"
onMouseEnter={() => setShowDropdownProjects(true)}
onMouseLeave={() => setShowDropdownProjects(false)}
>
<motion.span
className="text-2xl text-white hover:text-blue-300 transition-colors py-2 px-6 rounded-lg hover:bg-white/5 cursor-pointer select-none"
variants={ANIMATIONS.menuItem}
>
Projects
</motion.span>
<AnimatePresence>
{showDropdownProjects && (
<motion.div
className="absolute top-full left-0 mt-2 flex flex-col bg-white/10 border border-white/20 rounded-lg shadow-lg"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
>
<a
href="/upload"
className="px-4 py-2 text-white hover:bg-blue-500/20"
onClick={() => setIsOpen(false)}
>
Upload Projects
</a>
<a
href="/projects"
className="px-4 py-2 text-white hover:bg-blue-500/20"
onClick={() => setIsOpen(false)}
>
View Projects
</a>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
if (item === "Resources") {
return (
<div
key="resources-desktop"
key="resources"
className="relative group"
onMouseEnter={() => setShowDropdown(true)}
onMouseLeave={() => setShowDropdown(false)}
onMouseEnter={() => setShowDropdownResources(true)}
onMouseLeave={() => setShowDropdownResources(false)}
>
<motion.span
className="text-2xl text-white hover:text-blue-300 transition-colors py-2 px-6 rounded-lg hover:bg-white/5"
className="text-2xl text-white hover:text-blue-300 transition-colors py-2 px-6 rounded-lg hover:bg-white/5 cursor-pointer select-none"
variants={ANIMATIONS.menuItem}
>
Resources
</motion.span>
<AnimatePresence>
{showDropdown && (
{showDropdownResources && (
<motion.div
className="absolute top-full left-0 mt-2 flex flex-col bg-white/10 border border-white/20 rounded-lg shadow-lg"
initial={{ opacity: 0, y: -10 }}
@@ -258,12 +342,14 @@ export default function Navbar() {
<a
href="/onlineresources"
className="px-4 py-2 text-white hover:bg-blue-500/20"
onClick={() => setIsOpen(false)}
>
Online Resources
</a>
<a
href="/books"
className="px-4 py-2 text-white hover:bg-blue-500/20"
onClick={() => setIsOpen(false)}
>
Books
</a>

135
src/app/projects/page.tsx Normal file
View File

@@ -0,0 +1,135 @@
'use client';
import React, { useEffect, useState } from "react";
import { Orbitron } from "next/font/google";
import { FaGithub } from "react-icons/fa";
const orbitron = Orbitron({
subsets: ["latin"],
weight: ["400", "700"],
});
const cardThemes = [
{
bg: "from-[#0F172A] to-[#1E293B]",
headerBg: "bg-[#1E3A8A]",
accent: "#60A5FA",
},
{
bg: "from-[#0A0F1E] to-[#112240]",
headerBg: "bg-[#0EA5E9]",
accent: "#38BDF8",
},
{
bg: "from-[#1E293B] to-[#0F172A]",
headerBg: "bg-[#0284C7]",
accent: "#7DD3FC",
},
];
const ProjectsPage: React.FC = () => {
const [projects, setProjects] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const fetchProjects = async () => {
try {
const res = await fetch("/api/projects", { cache: "no-store" });
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to fetch projects");
setProjects(data);
} catch (err: any) {
console.error("Error fetching projects:", err);
setError(err.message || "Something went wrong");
} finally {
setLoading(false);
}
};
fetchProjects();
}, []);
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center text-cyan-400 text-2xl font-bold">
Loading Projects...
</div>
);
}
if (error) {
return (
<div className="min-h-screen flex items-center justify-center text-red-500 text-xl font-semibold">
{error}
</div>
);
}
return (
<div className="min-h-screen w-full bg-gradient-to-b from-[#050816] via-[#0A0F1E] to-[#0F172A] py-16 px-4 sm:px-8">
<h1
className={`text-4xl sm:text-5xl font-bold text-center text-cyan-400 mb-12 ${orbitron.className}`}
>
PROJECTS
</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto">
{projects.map((project, index) => {
const theme = cardThemes[index % cardThemes.length];
return (
<div
key={project._id || index}
className={`rounded-xl overflow-hidden border border-cyan-500/20 shadow-[0_0_25px_rgba(0,255,255,0.1)] hover:shadow-[0_0_35px_rgba(0,255,255,0.25)] transition duration-300 bg-gradient-to-br ${theme.bg}`}
>
<div className={`${theme.headerBg} py-4 px-6 text-center border-b border-cyan-400/30`}>
<h2 className={`text-2xl font-bold text-white tracking-wide ${orbitron.className}`}>
{project.name}
</h2>
</div>
<div className="p-6 text-white flex flex-col gap-3">
<p className="text-gray-300">
<strong>Tech Stack:</strong> {project.techStack}
</p>
{project.description && (
<p className="text-gray-400 text-sm italic">{project.description}</p>
)}
<div className="flex items-center justify-between mt-4">
{project.deployedLink && (
<a
href={project.deployedLink}
target="_blank"
rel="noopener noreferrer"
style={{ color: theme.accent }}
className="underline hover:opacity-80 transition duration-200"
>
View Project
</a>
)}
{project.githubLink && (
<a
href={project.githubLink}
target="_blank"
rel="noopener noreferrer"
className="text-2xl hover:scale-110 transition-transform"
style={{ color: theme.accent }}
>
<FaGithub />
</a>
)}
</div>
</div>
</div>
);
})}
</div>
</div>
);
};
export default ProjectsPage;

197
src/app/upload/page.tsx Normal file
View File

@@ -0,0 +1,197 @@
'use client';
import React, { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
const UploadPage: React.FC = () => {
const router = useRouter();
const [formData, setFormData] = useState({
projectName: "",
techStack: "",
description: "",
github: "",
deployed: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [message, setMessage] = useState("");
const [showModal, setShowModal] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({ ...formData, [e.target.id]: e.target.value });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setMessage("");
try {
const res = await fetch("/api/projects", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: formData.projectName,
techStack: formData.techStack,
description: formData.description,
githubLink: formData.github,
deployedLink: formData.deployed,
}),
});
if (res.ok) {
setMessage(" Project submitted successfully!");
setShowModal(true);
setFormData({
projectName: "",
techStack: "",
description: "",
github: "",
deployed: "",
});
// Redirect after 2 seconds
setTimeout(() => {
setShowModal(false);
router.push("/projects");
}, 2000);
} else {
setMessage(" Failed to submit. Try again.");
}
} catch (err) {
console.error(err);
setMessage(" Something went wrong.");
} finally {
setIsSubmitting(false);
}
};
return (
<div className="min-h-screen flex flex-col md:flex-row">
<div className="hidden md:block md:w-1/2 relative">
<Image
src="/clg.jpg"
alt="ml4e"
fill
style={{ objectFit: "cover" }}
priority
/>
</div>
<div className="w-full md:w-1/2 min-h-screen flex items-center justify-center p-6 sm:p-10 bg-gradient-to-b from-[#0A0A23] via-[#1B1B3A] to-[#3C4EC0]">
<form
onSubmit={handleSubmit}
className="w-full max-w-xl text-white p-6 sm:p-8 rounded-xl border border-blue-500 shadow-[0_0_15px_2px_rgba(100,149,237,0.4)] backdrop-blur-sm"
>
<h2 className="text-3xl font-bold mb-8 text-center">Submit Your Project</h2>
<div className="mb-6">
<label htmlFor="projectName" className="block mb-2 text-lg font-semibold">
Project Name
</label>
<input
id="projectName"
type="text"
required
value={formData.projectName}
onChange={handleChange}
placeholder="Project name"
className="w-full p-3 rounded bg-transparent border border-white placeholder-gray-400 text-white focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
</div>
<div className="mb-6">
<label htmlFor="techStack" className="block mb-2 text-lg font-semibold">
Tech Stack
</label>
<input
id="techStack"
type="text"
required
value={formData.techStack}
onChange={handleChange}
placeholder="React, Node.js, MongoDB, FASTAPI, etc."
className="w-full p-3 rounded bg-transparent border border-white placeholder-gray-400 text-white focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
</div>
<div className="mb-6">
<label htmlFor="description" className="block mb-2 text-lg font-semibold">
Description
</label>
<textarea
id="description"
required
value={formData.description}
onChange={handleChange}
placeholder="Brief description of your project..."
rows={4}
className="w-full p-3 rounded bg-transparent border border-white placeholder-gray-400 text-white focus:outline-none focus:ring-2 focus:ring-blue-400"
></textarea>
</div>
<div className="mb-6">
<label htmlFor="github" className="block mb-2 text-lg font-semibold">
GitHub Repository Link
</label>
<input
id="github"
type="url"
value={formData.github}
onChange={handleChange}
placeholder="https://github.com/yourproject"
className="w-full p-3 rounded bg-transparent border border-white placeholder-gray-400 text-white focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
</div>
<div className="mb-8">
<label htmlFor="deployed" className="block mb-2 text-lg font-semibold">
Deployed Project Link
</label>
<input
id="deployed"
type="url"
value={formData.deployed}
onChange={handleChange}
placeholder="https://yourproject.vercel.app"
className="w-full p-3 rounded bg-transparent border border-white placeholder-gray-400 text-white focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
</div>
<button
type="submit"
disabled={isSubmitting}
className={`w-full py-3 cursor-pointer rounded font-bold transition ${
isSubmitting
? "bg-gray-500 cursor-not-allowed"
: "bg-white text-[#0A0A23] hover:bg-blue-400 hover:text-white"
}`}
>
{isSubmitting ? "Submitting..." : "Submit Project"}
</button>
{message && (
<p className="text-center mt-4 text-blue-300 font-semibold">{message}</p>
)}
</form>
</div>
{showModal && (
<div className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50">
<div className="bg-blue-600 text-white rounded-xl shadow-xl p-8 max-w-sm w-full text-center animate-fade-in">
<h2 className="text-2xl font-bold mb-4"> Project Submitted!</h2>
<p className="text-gray-200 mb-6">
Redirecting you to the projects page...
</p>
<div className="flex justify-center">
<div className="w-6 h-6 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
</div>
</div>
</div>
)}
</div>
);
};
export default UploadPage;

16
src/lib/db.js Normal file
View File

@@ -0,0 +1,16 @@
import mongoose from "mongoose";
const connectToDatabase = async () => {
if (mongoose.connection.readyState >= 1) return;
try {
await mongoose.connect(process.env.MongoURL, {
dbName: "portfolioDB",
});
console.log(" Connected to MongoDB");
} catch (error) {
console.error(" MongoDB connection error:", error);
}
};
export default connectToDatabase;

14
src/models/Projects.js Normal file
View File

@@ -0,0 +1,14 @@
import mongoose from "mongoose";
const ProjectSchema = new mongoose.Schema(
{
name: { type: String, required: true },
techStack: { type: String, required: true },
description: { type: String, required: true },
deployedLink: { type: String, required: true },
githubLink: { type: String, required: true },
},
{ timestamps: true }
);
export default mongoose.models.Project || mongoose.model("Project", ProjectSchema);