Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e093211

Browse files
author
darealosc
committed
Working On Login
1 parent e22cee6 commit e093211

26,836 files changed

Lines changed: 1131285 additions & 19 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/api.js

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ const bodyParser = require("body-parser");
44
const { createServer } = require('node:http');
55
const { join } = require('node:path');
66
const { Server } = require('socket.io');
7-
const { create } = require("node:domain");
7+
const net = require('node:net');
88
require('dotenv').config({ path: require('node:path').join(__dirname, '.env') });
99

1010
const app = express();
11-
const PORT = process.env.PORT || 3000;
11+
const PORT = Number(process.env.PORT) || 3000;
1212

1313
// Import controllers
1414
const { getCourses, getGrades, getAssignments } = require('./controllers/CourseController');
15+
const { login } = require('./controllers/LoginController');
1516
const { askAI } = require('./controllers/AIController');
1617

1718
// Enable CORS for all routes, allow all origins
@@ -26,6 +27,7 @@ app.use(bodyParser.json());
2627
app.get('/courses', getCourses);
2728
app.get('/grades/:courseId', getGrades);
2829
app.get('/courses/:courseId/assignments', getAssignments);
30+
app.post('/api/login', login);
2931
app.post('/ai/ask', askAI);
3032

3133

@@ -63,20 +65,43 @@ io.on("connection", (socket) => {
6365

6466

6567

68+
// 🟢 Start server with dynamic port selection to avoid EADDRINUSE
69+
function getAvailablePort(startPort, attempts = 20) {
70+
return new Promise((resolve, reject) => {
71+
let port = startPort;
72+
const tryPort = () => {
73+
if (attempts <= 0) return reject(new Error('No available ports'));
74+
const tester = net.createServer()
75+
.once('error', (err) => {
76+
if (err.code === 'EADDRINUSE') {
77+
attempts -= 1;
78+
port += 1;
79+
tryPort();
80+
} else {
81+
reject(err);
82+
}
83+
})
84+
.once('listening', () => {
85+
tester.close(() => resolve(port));
86+
})
87+
.listen(port, '0.0.0.0');
88+
};
89+
tryPort();
90+
});
91+
}
6692

67-
68-
69-
70-
71-
72-
73-
74-
75-
// 🟢 Start server
76-
server.listen(PORT, () => {
77-
console.log(`server running at http://localhost:${PORT}`);
78-
console.log(`socket.io mounted at http://localhost:${PORT}/chat`);
79-
});
93+
(async () => {
94+
try {
95+
const chosenPort = await getAvailablePort(PORT);
96+
server.listen(chosenPort, () => {
97+
console.log(`server running at http://localhost:${chosenPort}`);
98+
console.log(`socket.io mounted at http://localhost:${chosenPort}/chat`);
99+
});
100+
} catch (err) {
101+
console.error('Failed to start server:', err.message || err);
102+
process.exit(1);
103+
}
104+
})();
80105

81106

82107

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Dummy login controller for POST /api/login
2+
// Accepts { username, password, email } and returns success if username is 'user' and password is 'pass'
3+
4+
const login = (req, res) => {
5+
const { username, password, email } = req.body || {};
6+
if (username === 'user' && password === 'pass') {
7+
return res.json({ success: true, message: 'Login successful!' });
8+
}
9+
return res.status(401).json({ success: false, error: 'Invalid credentials' });
10+
};
11+
12+
module.exports = { login };

frontend/app/globals.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,12 @@ body {
2424
color: var(--foreground);
2525
font-family: Arial, Helvetica, sans-serif;
2626
}
27+
28+
@import "tailwindcss";
29+
30+
@theme inline {
31+
--shadow-input:
32+
0px 2px 3px -1px rgba(0, 0, 0, 0.1),
33+
0px 1px 0px 0px rgba(25, 28, 33, 0.02),
34+
0px 0px 0px 1px rgba(25, 28, 33, 0.08);
35+
}

frontend/app/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11

22
"use client";
33
import { useEffect, useState } from "react";
4-
4+
import Sidebar from "../components/sidebar"
5+
import { LogIn } from "../components/LogIn";
56
export default function Home() {
67
return (
78
<div>
8-
9+
<LogIn />
910
</div>
1011
);
1112
}

frontend/components/LogIn.tsx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"use client";
2+
import React, { useState } from "react";
3+
import { Label } from "./ui/label";
4+
import { Input } from "./ui/input";
5+
import { cn } from "@/lib/utils";
6+
import {
7+
IconBrandGithub,
8+
IconBrandGoogle,
9+
} from "@tabler/icons-react";
10+
11+
export function LogIn() {
12+
const [username, setUsername] = useState("");
13+
const [password, setPassword] = useState("");
14+
const [email, setEmail] = useState("");
15+
const [loading, setLoading] = useState(false);
16+
const [error, setError] = useState("");
17+
const [success, setSuccess] = useState("");
18+
19+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
20+
e.preventDefault();
21+
setError("");
22+
setSuccess("");
23+
setLoading(true);
24+
try {
25+
const res = await fetch("/api/login", {
26+
method: "POST",
27+
headers: { "Content-Type": "application/json" },
28+
body: JSON.stringify({ username, password, email }),
29+
});
30+
const data = await res.json();
31+
if (res.ok && data.success) {
32+
setSuccess("Login successful!");
33+
} else {
34+
setError(data.error || "Login failed");
35+
}
36+
} catch (err) {
37+
setError("Network error. Please try again.");
38+
} finally {
39+
setLoading(false);
40+
}
41+
};
42+
43+
return (
44+
<div className="min-h-screen flex items-center justify-center bg-black">
45+
<div className="shadow-input w-full max-w-xs rounded-2xl bg-zinc-900 p-6 flex flex-col items-center">
46+
<h2 className="text-lg font-bold text-white text-center mb-1">Login</h2>
47+
<form className="w-full space-y-3" onSubmit={handleSubmit}>
48+
<div className="flex space-x-2">
49+
<input
50+
type="text"
51+
placeholder="Username"
52+
className="flex-1 rounded-lg bg-zinc-800 p-2 text-white placeholder-zinc-500 text-xs"
53+
value={username}
54+
onChange={e => setUsername(e.target.value)}
55+
required
56+
/>
57+
</div>
58+
<input
59+
type="password"
60+
placeholder="Password"
61+
className="w-full rounded-lg bg-zinc-800 p-2 text-white placeholder-zinc-500 text-xs"
62+
value={password}
63+
onChange={e => setPassword(e.target.value)}
64+
required
65+
/>
66+
<input
67+
type="email"
68+
placeholder="Email"
69+
className="w-full rounded-lg bg-zinc-800 p-2 text-white placeholder-zinc-500 text-xs"
70+
value={email}
71+
onChange={e => setEmail(e.target.value)}
72+
required
73+
/>
74+
<button
75+
type="submit"
76+
className="w-full bg-zinc-800 hover:bg-zinc-700 text-white font-semibold py-2 rounded-lg transition text-xs mt-2"
77+
disabled={loading}
78+
>
79+
{loading ? "Logging in..." : "Log In →"}
80+
</button>
81+
{error && <div className="text-xs text-red-400 text-center mt-2">{error}</div>}
82+
{success && <div className="text-xs text-green-400 text-center mt-2">{success}</div>}
83+
</form>
84+
</div>
85+
</div>
86+
);
87+
}
88+
89+
const BottomGradient = () => {
90+
return (
91+
<>
92+
<span className="absolute inset-x-0 -bottom-px block h-px w-full bg-gradient-to-r from-transparent via-cyan-500 to-transparent opacity-0 transition duration-500 group-hover/btn:opacity-100" />
93+
<span className="absolute inset-x-10 -bottom-px mx-auto block h-px w-1/2 bg-gradient-to-r from-transparent via-indigo-500 to-transparent opacity-0 blur-sm transition duration-500 group-hover/btn:opacity-100" />
94+
</>
95+
);
96+
};
97+
98+
const LabelInputContainer = ({
99+
children,
100+
className,
101+
}: {
102+
children: React.ReactNode;
103+
className?: string;
104+
}) => {
105+
return (
106+
<div className={cn("flex w-full flex-col space-y-2", className)}>
107+
{children}
108+
</div>
109+
);
110+
};

frontend/components/sidebar.tsx

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"use client";
2+
import { Sparkles, Home, Menu, X, Shield, FileText, MessageSquare } from "lucide-react";
3+
import { useState } from "react";
4+
import { motion, AnimatePresence } from "framer-motion";
5+
import { useRouter } from "next/navigation";
6+
7+
interface SidebarProps {
8+
onOpenAIClick?: () => void;
9+
}
10+
11+
export default function Sidebar({ onOpenAIClick }: SidebarProps) {
12+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
13+
const router = useRouter();
14+
15+
return (
16+
<>
17+
{/* Mobile Menu Button */}
18+
<button
19+
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
20+
className="md:hidden fixed top-5 left-4 z-50 w-12 h-12 bg-[#1c1e2b] rounded-xl flex items-center justify-center border border-zinc-700/50 shadow-lg hover:bg-[#252736] transition-colors"
21+
>
22+
{isMobileMenuOpen ? (
23+
<X size={22} className="text-white" />
24+
) : (
25+
<Menu size={22} className="text-white" />
26+
)}
27+
</button>
28+
29+
{/* Mobile Overlay */}
30+
<AnimatePresence>
31+
{isMobileMenuOpen && (
32+
<motion.div
33+
initial={{ opacity: 0 }}
34+
animate={{ opacity: 1 }}
35+
exit={{ opacity: 0 }}
36+
className="md:hidden fixed inset-0 bg-black/50 z-40"
37+
onClick={() => setIsMobileMenuOpen(false)}
38+
/>
39+
)}
40+
</AnimatePresence>
41+
42+
{/* Sidebar */}
43+
<motion.div
44+
initial={false}
45+
animate={{
46+
x: isMobileMenuOpen ? 0 : -320,
47+
}}
48+
transition={{ type: "spring", damping: 25, stiffness: 200 }}
49+
className="fixed top-0 left-0 bottom-0 md:top-6 md:left-6 w-[280px] md:h-[calc(100vh-48px)] bg-[#1c1e2b] md:rounded-lg flex flex-col overflow-hidden shadow-2xl border-r md:border border-zinc-800/50 z-50 md:!transform-none"
50+
>
51+
<div className="flex-1 overflow-y-auto p-4 space-y-2">
52+
{/* Home */}
53+
<div
54+
onClick={() => {
55+
window.location.reload();
56+
setIsMobileMenuOpen(false);
57+
}}
58+
className="flex items-center gap-3 px-4 py-3 bg-zinc-700/30 hover:bg-zinc-700/50 rounded-xl cursor-pointer transition-all min-h-[52px] w-full"
59+
>
60+
<div className="w-8 h-8 flex items-center justify-center">
61+
<Home size={20} className="text-zinc-300" />
62+
</div>
63+
<span className="text-white font-medium text-base">Home!</span>
64+
</div>
65+
{/* AI */}
66+
<div
67+
onClick={() => {
68+
onOpenAIClick?.();
69+
setIsMobileMenuOpen(false);
70+
}}
71+
className="flex items-center gap-3 px-4 py-3 bg-purple-500/20 hover:bg-purple-500/30 border border-purple-500/30 rounded-xl cursor-pointer transition-all min-h-[52px] w-full"
72+
>
73+
<div className="w-8 h-8 flex items-center justify-center">
74+
<Sparkles size={20} className="text-purple-400" />
75+
</div>
76+
<span className="text-white font-medium text-base">AI</span>
77+
</div>
78+
79+
{/* Divider removed: legal/contact moved to bottom icon row */}
80+
</div>
81+
{/* Bottom icon row */}
82+
<div className="border-t border-zinc-800/50 p-3 bg-[#1c1e2b] flex items-center justify-around">
83+
<button
84+
aria-label="Privacy Policy"
85+
title="Privacy Policy"
86+
onClick={() => {
87+
router.push("/privacy");
88+
setIsMobileMenuOpen(false);
89+
}}
90+
className="w-11 h-11 rounded-lg flex items-center justify-center hover:bg-zinc-700/30 transition-colors"
91+
>
92+
<Shield size={20} className="text-zinc-300" />
93+
</button>
94+
<button
95+
aria-label="Terms of Service"
96+
title="Terms of Service"
97+
onClick={() => {
98+
router.push("/terms");
99+
setIsMobileMenuOpen(false);
100+
}}
101+
className="w-11 h-11 rounded-lg flex items-center justify-center hover:bg-zinc-700/30 transition-colors"
102+
>
103+
<FileText size={20} className="text-zinc-300" />
104+
</button>
105+
<button
106+
aria-label="Contact Us"
107+
title="Contact Us"
108+
onClick={() => {
109+
router.push("/contact");
110+
setIsMobileMenuOpen(false);
111+
}}
112+
className="w-11 h-11 rounded-lg flex items-center justify-center hover:bg-zinc-700/30 transition-colors"
113+
>
114+
<MessageSquare size={20} className="text-zinc-300" />
115+
</button>
116+
</div>
117+
</motion.div>
118+
</>
119+
);
120+
}

0 commit comments

Comments
 (0)