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
110 changes: 110 additions & 0 deletions css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -3130,6 +3130,116 @@ body {
font-weight: 600;
}

.password-field {
position: relative;
margin-bottom: 20px;
}

.password-field input {
width: 100%;
padding: 12px 44px 12px 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
box-sizing: border-box;
}

.password-toggle-btn {
position: absolute;
top: 50%;
right: 10px;
width: 32px;
height: 32px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--color-text-secondary);
cursor: pointer;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
}

.password-toggle-btn:hover {
background: rgba(0, 0, 0, 0.06);
color: var(--color-text-primary);
}

.profile-modal {
display: none;
position: fixed;
inset: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
align-items: center;
justify-content: center;
padding: 20px;
}

.profile-modal.is-open {
display: flex;
}

.profile-card {
position: relative;
width: min(360px, 100%);
padding: 32px;
border: 1px solid var(--color-border-tertiary);
border-radius: var(--border-radius-lg);
background: var(--color-background-primary);
color: var(--color-text-primary);
box-shadow: var(--shadow-lg);
text-align: center;
}

.profile-close-btn {
position: absolute;
top: 12px;
right: 14px;
border: none;
background: transparent;
color: var(--color-text-secondary);
font-size: 24px;
line-height: 1;
cursor: pointer;
}

.profile-avatar {
width: 72px;
height: 72px;
margin: 0 auto 16px;
border-radius: 50%;
background: var(--color-text-primary);
color: var(--color-background-primary);
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
font-weight: 700;
}

.profile-card h2 {
margin: 0 0 20px;
font-size: 22px;
}

.profile-label {
margin-bottom: 6px;
color: var(--color-text-tertiary);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
}

.profile-email {
color: var(--color-text-secondary);
font-size: 15px;
overflow-wrap: anywhere;
}

/* Footer */
.site-footer {
width: 100%;
Expand Down
114 changes: 18 additions & 96 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ <h2 id="auth-title" style="margin:0 0 8px; font-size:22px; font-weight:700;">Wel

<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;">
<div class="password-field">
<input id="auth-password" type="password" placeholder="Password">
<button id="toggle-password-btn" class="password-toggle-btn" type="button" aria-label="Show password" title="Show password">
<i class="fa-regular fa-eye" aria-hidden="true"></i>
</button>
</div>

<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
Expand All @@ -44,7 +49,6 @@ <h2 id="auth-title" style="margin:0 0 8px; font-size:22px; font-weight:700;">Wel
<a id="auth-toggle-btn" href="#" style="color:#4f46e5; font-weight:600; margin-left:4px;">Sign Up</a>
</p>

<p id="auth-error" style="color:red; font-size:13px; text-align:center; margin:8px 0 0; display:none;"></p>
</div>
</div>

Expand Down Expand Up @@ -98,7 +102,7 @@ <h1 class="site-title">StudyPlan</h1>
/>
</div>
</div>
<button class="profile-btn">Profile</button>
<button class="profile-btn" id="profile-btn" type="button">Profile</button>
<button class="profile-btn" id="logout-btn">Logout</button>
</div>
</header>
Expand Down Expand Up @@ -314,6 +318,17 @@ <h1 class="site-title">StudyPlan</h1>
</div>
</footer>

<!-- Profile Modal -->
<div id="profile-modal" class="profile-modal" aria-hidden="true">
<div class="profile-card" role="dialog" aria-modal="true" aria-labelledby="profile-title">
<button id="profile-close" class="profile-close-btn" type="button" aria-label="Close profile">&times;</button>
<div class="profile-avatar" aria-hidden="true">SP</div>
<h2 id="profile-title">User Profile</h2>
<p class="profile-label">Email</p>
<p id="profile-email" class="profile-email">Not signed in</p>
</div>
</div>

<!-- 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);">
Expand Down Expand Up @@ -366,99 +381,6 @@ <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;

document.getElementById('auth-toggle-btn').addEventListener('click', (e) => {
e.preventDefault();
isLogin = !isLogin;
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';
});

// 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('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';

// Empty fields check
if (!email || !password) {
errorEl.textContent = 'Please fill in all fields';
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;
}

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 })
});
const data = await res.json();

if (!res.ok) {
errorEl.textContent = data.error || 'Something went wrong';
errorEl.style.display = 'block';
return;
}

localStorage.setItem('studyplan_user', JSON.stringify({ email: data.email }));
document.getElementById('auth-modal').style.display = 'none';

} catch (err) {
errorEl.textContent = 'Network error. Please try again.';
errorEl.style.display = 'block';
}
});

// Check if already logged in
if (localStorage.getItem('studyplan_user')) {
document.getElementById('auth-modal').style.display = 'none';
}

// Logout Button Functionality
const logoutBtn = document.getElementById('logout-btn');
logoutBtn.addEventListener('click', () => {
localStorage.removeItem('studyplan_user');

// Show login modal again
document.getElementById('auth-modal').style.display = 'flex';

// Refresh page state
window.location.reload();
});

// Settings Modal Logic
const settingsModal = document.getElementById('settings-modal');
const navSettings = document.getElementById('nav-settings');
Expand Down
84 changes: 81 additions & 3 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1714,14 +1714,64 @@ const authError = document.getElementById('auth-error');

const emailInput = document.getElementById('auth-email');
const passwordInput = document.getElementById('auth-password');
const passwordToggleBtn = document.getElementById('toggle-password-btn');
const profileBtn = document.getElementById('profile-btn');
const profileModal = document.getElementById('profile-modal');
const profileCloseBtn = document.getElementById('profile-close');
const profileEmail = document.getElementById('profile-email');

const logoutBtn = document.getElementById('logout-btn');

let isLoginMode = true;

function getSavedUser() {
const saved = localStorage.getItem('studyplan_user');

if (!saved) {
return null;
}

try {
const parsed = JSON.parse(saved);
return typeof parsed === 'object' && parsed !== null ? parsed : { email: saved };
} catch (err) {
return { email: saved };
}
}

function getUserInitials(email) {
if (!email) {
return 'SP';
}

return email
.split('@')[0]
.split(/[._-]+/)
.filter(Boolean)
.slice(0, 2)
.map(part => part.charAt(0).toUpperCase())
.join('') || 'SP';
}

function showProfile() {
const user = getSavedUser();
const email = user?.email || 'Not signed in';
const avatar = profileModal.querySelector('.profile-avatar');

profileEmail.textContent = email;
avatar.textContent = getUserInitials(email);
profileModal.classList.add('is-open');
profileModal.setAttribute('aria-hidden', 'false');
}

function hideProfile() {
profileModal.classList.remove('is-open');
profileModal.setAttribute('aria-hidden', 'true');
}

// ================= CHECK LOGIN =================

const savedUser = localStorage.getItem('studyplan_user');
const savedUser = getSavedUser();

if (savedUser) {
authModal.style.display = 'none';
Expand Down Expand Up @@ -1753,6 +1803,34 @@ authToggleBtn.addEventListener('click', (e) => {
}
});

// ================= PASSWORD VISIBILITY =================

passwordToggleBtn.addEventListener('click', () => {
const isHidden = passwordInput.type === 'password';
const icon = passwordToggleBtn.querySelector('i');

passwordInput.type = isHidden ? 'text' : 'password';
passwordToggleBtn.setAttribute('aria-label', isHidden ? 'Hide password' : 'Show password');
passwordToggleBtn.setAttribute('title', isHidden ? 'Hide password' : 'Show password');
icon.className = isHidden ? 'fa-regular fa-eye-slash' : 'fa-regular fa-eye';
});

// ================= PROFILE =================

profileBtn.addEventListener('click', showProfile);
profileCloseBtn.addEventListener('click', hideProfile);
profileModal.addEventListener('click', (event) => {
if (event.target === profileModal) {
hideProfile();
}
});

document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && profileModal.classList.contains('is-open')) {
hideProfile();
}
});

// ================= LOGIN / SIGNUP =================

authSubmitBtn.addEventListener('click', async () => {
Expand Down Expand Up @@ -1792,7 +1870,7 @@ authSubmitBtn.addEventListener('click', async () => {
}

// Save logged-in user
localStorage.setItem('studyplan_user', email);
localStorage.setItem('studyplan_user', JSON.stringify({ email: data.email || email }));

authModal.style.display = 'none';

Expand Down Expand Up @@ -1829,4 +1907,4 @@ if (calendarDownloadBtn) {
calendarDownloadBtn.addEventListener('click', () => {
downloadCalendar();
});
}
}
Loading