projects
This commit is contained in:
210
package-lock.json
generated
210
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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
BIN
public/ai.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 709 KiB |
BIN
public/clg.jpg
Normal file
BIN
public/clg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 674 KiB |
41
src/app/api/projects/route.js
Normal file
41
src/app/api/projects/route.js
Normal 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 });
|
||||
}
|
||||
}
|
||||
@@ -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
135
src/app/projects/page.tsx
Normal 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
197
src/app/upload/page.tsx
Normal 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
16
src/lib/db.js
Normal 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
14
src/models/Projects.js
Normal 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);
|
||||
Reference in New Issue
Block a user