Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified css/index.css
Binary file not shown.
169 changes: 104 additions & 65 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,23 @@
</head>
<body>
<!-- Auth Modal -->
<div id="auth-modal" style="display:flex; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:9999; align-items:center; justify-content:center;">
<div style="background:white; border-radius:16px; padding:32px; width:360px; box-shadow:0 20px 60px rgba(0,0,0,0.3);">
<div id="auth-modal" class="modal-backdrop" style="display:flex; position:fixed; inset:0; z-index:9999; align-items:center; justify-content:center;">
<div class="modal-card" style="border-radius:16px; padding:32px; width:360px; box-shadow:0 20px 60px rgba(0,0,0,0.3);">

<h2 id="auth-title" style="margin:0 0 8px; font-size:22px; font-weight:700;">Welcome back</h2>
<p id="auth-subtitle" style="margin:0 0 24px; color:#666; font-size:14px;">Sign in to your StudyPlan account</p>

<input id="auth-email" type="email" placeholder="Email address" style="width:100%; padding:12px; border:1px solid #ddd; border-radius:8px; font-size:14px; margin-bottom:12px; box-sizing:border-box;">

<input id="auth-password" type="password" placeholder="Password" style="width:100%; padding:12px; border:1px solid #ddd; border-radius:8px; font-size:14px; margin-bottom:20px; box-sizing:border-box;">
<input id="auth-password" type="password" placeholder="Password" style="width:100%; padding:12px; border:1px solid #ddd; border-radius:8px; font-size:14px; margin-bottom:12px; box-sizing:border-box;">

<button id="auth-submit-btn" style="width:100%; padding:12px; background:#4f46e5; color:white; border:none; border-radius:8px; font-size:15px; font-weight:600; cursor:pointer;">
Sign In
</button>

<div style="font-size:12px; color:#666; margin-top:10px; line-height:1.6;">
Password must contain:
<ul style="padding-left:18px; margin-top:6px;">
<li>Minimum 8 characters</li>
<li>At least 1 capital letter</li>
<li>At least 1 special character</li>
</ul>
<div style="text-align: right; margin-bottom: 20px;">
<a id="forgot-password-link" href="#" style="color:#4f46e5; font-size:13px; font-weight:500; text-decoration:none;">Forgot Password?</a>
</div>

<p id="auth-error" style="color:red; font-size:13px; text-align:center; margin:8px 0 0; display:none;"></p>
<button id="auth-submit-btn" style="width:100%; padding:12px; background:#4f46e5; color:white; border:none; border-radius:8px; font-size:15px; font-weight:600; cursor:pointer;">
Sign In
</button>

<p style="text-align:center; margin:16px 0 0; font-size:13px; color:#666;">
<span id="auth-toggle-text">Don't have an account?</span>
Expand All @@ -48,10 +41,13 @@ <h2 id="auth-title" style="margin:0 0 8px; font-size:22px; font-weight:700;">Wel
</div>

<header class="site-header">
<div class="header-left">
<img src="/logo.png" alt="Logo" class="logo">
<h1 class="site-title">StudyPlan</h1>
</div>
<div class="header-left">
<button id="mobile-menu-btn" class="mobile-menu-btn" aria-label="Toggle Menu">
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
</button>
<img src="/logo.png" alt="Logo" class="logo">
<h1 class="site-title">StudyPlan</h1>
</div>

<nav class="header-nav">
<a href="#">Dashboard</a>
Expand Down Expand Up @@ -245,15 +241,15 @@ <h1 class="site-title">StudyPlan</h1>
<footer class="site-footer">
<p>© 2026 StudyPlan. All rights reserved.</p>
<div class="footer-links">
<a href="/support-page/privacy.html">Privacy</a>
<a href="/support-page/terms.html">Terms</a>
<a href="/support-page/support.html">Support</a>
<a href="/support-page/index.html#privacy">Privacy</a>
<a href="/support-page/index.html#terms">Terms</a>
<a href="/support-page/index.html#support">Support</a>
</div>
</footer>

<!-- Settings Modal -->
<div id="settings-modal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:9999; align-items:center; justify-content:center; backdrop-filter:blur(4px);">
<div style="background:var(--color-background-primary); border:1px solid var(--color-border-tertiary); border-radius:16px; padding:32px; width:400px; box-shadow:var(--shadow-lg); color:var(--color-text-primary);">
<div id="settings-modal" class="modal-backdrop" style="display:none; position:fixed; inset:0; z-index:9999; align-items:center; justify-content:center;">
<div class="modal-card" style="border-radius:16px; padding:32px; width:400px; box-shadow:var(--shadow-lg);">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
<h2 style="margin:0; font-size:20px; font-weight:700;">Settings</h2>
<button id="settings-close" style="background:none; border:none; font-size:24px; cursor:pointer; color:var(--color-text-secondary);">&times;</button>
Expand Down Expand Up @@ -304,68 +300,110 @@ <h3 style="font-size:12px; font-weight:700; text-transform:uppercase; color:var(
<script type="module" src="/js/app.js"></script>
<script>
let isLogin = true;
let isForgotPassword = false;

document.getElementById('auth-toggle-btn').addEventListener('click', (e) => {
e.preventDefault();
isLogin = !isLogin;
isForgotPassword = false;
document.getElementById('auth-password').style.display = 'block';
document.getElementById('forgot-password-link').style.display = isLogin ? 'inline' : 'none';
document.getElementById('auth-title').textContent = isLogin ? 'Welcome back' : 'Create account';
document.getElementById('auth-subtitle').textContent = isLogin ? 'Sign in to your StudyPlan account' : 'Start planning your studies';
document.getElementById('auth-submit-btn').textContent = isLogin ? 'Sign In' : 'Sign Up';
document.getElementById('auth-toggle-text').textContent = isLogin ? "Don't have an account?" : 'Already have an account?';
document.getElementById('auth-toggle-btn').textContent = isLogin ? 'Sign Up' : 'Sign In';
document.getElementById('auth-error').style.display = 'none';
document.getElementById('auth-error').style.color = 'red';
});

// Password validation function
function validatePassword(password) {

// Minimum 8 characters
const minLength = password.length >= 8;

// At least 1 capital letter
const hasCapital = /[A-Z]/.test(password);

// At least 1 special character
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);

return minLength && hasCapital && hasSpecial;
}
document.getElementById('forgot-password-link').addEventListener('click', (e) => {
e.preventDefault();
isForgotPassword = true;
document.getElementById('auth-title').textContent = 'Reset Password';
document.getElementById('auth-subtitle').textContent = 'Enter your email to receive a reset link';
document.getElementById('auth-password').style.display = 'none';
document.getElementById('forgot-password-link').style.display = 'none';
document.getElementById('auth-submit-btn').textContent = 'Send Reset Link';
document.getElementById('auth-toggle-text').textContent = 'Remember your password?';
document.getElementById('auth-toggle-btn').textContent = 'Sign In';
document.getElementById('auth-error').style.display = 'none';
});

document.getElementById('auth-submit-btn').addEventListener('click', async () => {
// Password validation function
function validatePassword(password) {
// Minimum 8 characters
const minLength = password.length >= 8;
// At least 1 capital letter
const hasCapital = /[A-Z]/.test(password);
// At least 1 special character
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return minLength && hasCapital && hasSpecial;
}

const email = document.getElementById('auth-email').value.trim();
const password = document.getElementById('auth-password').value.trim();
const errorEl = document.getElementById('auth-error');
document.getElementById('auth-submit-btn').addEventListener('click', async () => {
const email = document.getElementById('auth-email').value.trim();
const password = document.getElementById('auth-password').value.trim();
const errorEl = document.getElementById('auth-error');

errorEl.style.display = 'none';
if (!email || (!password && !isForgotPassword)) {
errorEl.textContent = 'Please fill in all fields';
errorEl.style.color = 'red';
errorEl.style.display = 'block';
return;
}

// Empty fields check
if (!email || !password) {
errorEl.textContent = 'Please fill in all fields';
errorEl.style.display = 'block';
return;
}
if (isForgotPassword) {
try {
const res = await fetch('/api/auth/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const data = await res.json();

if (res.ok) {
errorEl.textContent = data.message || 'Password reset link sent';
errorEl.style.color = 'green';
errorEl.style.display = 'block';
setTimeout(() => {
isLogin = false; // so click toggles to true
document.getElementById('auth-toggle-btn').click();
}, 2000);
} else {
errorEl.textContent = data.error || 'An error occurred';
errorEl.style.color = 'red';
errorEl.style.display = 'block';
}
} catch (err) {
errorEl.textContent = 'Network error. Please try again.';
errorEl.style.color = 'red';
errorEl.style.display = 'block';
}
return;
}

// Password validation check
if (!validatePassword(password)) {
errorEl.textContent =
'Password must be at least 8 characters long, contain 1 capital letter and 1 special character.';
errorEl.style.display = 'block';
return;
}
if (!isForgotPassword && !validatePassword(password)) {
errorEl.textContent =
'Password must be at least 8 characters long, contain 1 capital letter and 1 special character.';
errorEl.style.color = 'red';
errorEl.style.display = 'block';
return;
}

const endpoint = isLogin ? '/api/auth/login' : '/api/auth/signup';
const endpoint = isLogin ? '/api/auth/login' : '/api/auth/signup';

try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await res.json();

if (!res.ok) {
errorEl.textContent = data.error || 'Something went wrong';
errorEl.style.color = 'red';
errorEl.style.display = 'block';
return;
}
Expand All @@ -375,6 +413,7 @@ <h3 style="font-size:12px; font-weight:700; text-transform:uppercase; color:var(

} catch (err) {
errorEl.textContent = 'Network error. Please try again.';
errorEl.style.color = 'red';
errorEl.style.display = 'block';
}
});
Expand Down Expand Up @@ -489,8 +528,8 @@ <h3 style="margin:0 0 12px; font-size:18px; font-weight:600;">New subject</h3>
</div>
</div>

<div id="new-task-modal" class="modal-backdrop" style="display:none; position:fixed; inset:0; z-index:9998; align-items:center; justify-content:center;">
<div class="modal-card" style=" border-radius:12px; padding:20px; width:320px; box-shadow:0 12px 40px rgba(0,0,0,0.25);">
<div id="new-task-modal" class="modal-backdrop" style="display:none; position:fixed; inset:0; z-index:9998; align-items:center; justify-content:center;">
<div class="modal-card" style="border-radius:12px; padding:20px; width:320px; box-shadow:0 12px 40px rgba(0,0,0,0.25);">
<h3 style="margin:0 0 12px; font-size:18px; font-weight:600;">New task</h3>

<label style="display:block; font-size:12px; margin-bottom:4px;">Subject</label>
Expand Down
18 changes: 18 additions & 0 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1245,3 +1245,21 @@ if (quoteEl) {
calendarDownloadBtn.addEventListener('click', () => {
downloadCalendar();
});

// Mobile Menu Toggle
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const sidebarContainer = document.querySelector('.sidebar');
if (mobileMenuBtn && sidebarContainer) {
mobileMenuBtn.addEventListener('click', () => {
sidebarContainer.classList.toggle('mobile-open');
});

// Close sidebar when clicking outside on mobile
document.addEventListener('click', (e) => {
if (window.innerWidth <= 768 && sidebarContainer.classList.contains('mobile-open')) {
if (!sidebarContainer.contains(e.target) && !mobileMenuBtn.contains(e.target)) {
sidebarContainer.classList.remove('mobile-open');
}
}
});
}
12 changes: 12 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,18 @@ Text: "${text}"
// ================= AUTH =================
const users = {}; // Simple in-memory user store

app.post('/api/auth/forgot-password', (req, res) => {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: 'Email required' });
}
const user = users[email];
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json({ success: true, message: 'Password reset link sent to ' + email });
});

app.post('/api/auth/signup', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
Expand Down