Compare commits

..

8 Commits

Author SHA1 Message Date
hyzen
b05f1a731d Fix: otp verification 2026-06-07 00:30:37 +05:30
hyzen
28a6e82bfc Add: otp verification 2026-06-07 00:14:06 +05:30
hyzen
00b91c13da Update: cosmetic fix 2026-06-06 23:03:24 +05:30
hyzen
282ce882f8 Update: cosmetic fix 2026-06-06 21:27:37 +05:30
hyzen
62573b2893 Update: cosmetic fix 2026-06-06 20:37:20 +05:30
hyzen
55b2921ecf Update: cosmetic fix 2026-06-06 20:29:38 +05:30
hyzen
d0208d7d1a Update: cosmetic fix 2026-06-06 20:09:28 +05:30
hyzen
f758fc8bac Update: cosmetic fix 2026-06-06 19:43:58 +05:30
6 changed files with 329 additions and 78 deletions

View File

@@ -198,3 +198,41 @@
[data-theme='light'] .auth-form__submit {
color: #fff;
}
/* ── OTP panel ───────────────────────────────────────────────────────────── */
.otp-panel__hint {
font-size: 0.87rem;
line-height: 1.5;
color: var(--foreground-color);
margin: 0;
}
.otp-panel__input {
letter-spacing: 0.25em;
font-size: 1.15rem;
text-align: center;
}
.otp-panel__resend {
font-size: 0.82rem;
color: var(--foreground-color3, #888);
text-align: center;
margin: 0;
}
.otp-panel__resend-btn {
background: none;
border: none;
padding: 0;
font: inherit;
font-size: 0.82rem;
color: var(--accent-color);
cursor: pointer;
font-weight: 600;
text-decoration: underline;
}
.otp-panel__resend-btn:hover {
opacity: 0.8;
}

View File

@@ -252,7 +252,6 @@
@media (max-width: 600px) {
/* Profile button: sit above the subscribe button, right-aligned to it */
.brand__actions {
gap: 0.16rem;
position: relative;
}
@@ -470,6 +469,7 @@
cursor: pointer;
line-height: 1;
transition: opacity 0.2s ease;
margin-right: -0.17rem;
}
.theme-toggle:hover {
@@ -552,6 +552,7 @@
height: 2.3rem;
box-sizing: border-box;
padding: 0;
margin-right: -0.5rem;
}
.theme-toggle svg {

File diff suppressed because one or more lines are too long

View File

@@ -5,40 +5,41 @@
<div class="auth-card">
<div id="auth-message" class="auth-message" aria-live="polite" style="display: none"></div>
<!-- ── STEP 1 — Account details ───────────────────────────────── -->
<form id="signup-form" class="auth-form" novalidate>
<div class="auth-form__input-wrap">
<div class="auth-form__group">
<label class="auth-form__label" for="signup-username">Username</label>
<input
class="auth-form__input"
type="text"
id="signup-username"
name="username"
autocomplete="username"
required
minlength="3"
maxlength="32"
pattern="[a-zA-Z0-9_\-]+"
/>
<span class="auth-form__hint"
>(3-32 characters; letters, numbers, _ and - only)</span
>
</div>
<div class="auth-form__group">
<label class="auth-form__label" for="signup-username">Username</label>
<input
class="auth-form__input"
type="text"
id="signup-username"
name="username"
autocomplete="username"
required
minlength="3"
maxlength="32"
pattern="[a-zA-Z0-9_\-]+"
/>
<span class="auth-form__hint"
>(3-32 characters; letters, numbers, _ and - only)</span
>
</div>
<div class="auth-form__group">
<label class="auth-form__label" for="signup-email">Email</label>
<input
class="auth-form__input"
type="email"
id="signup-email"
name="email"
autocomplete="email"
required
/>
</div>
<div class="auth-form__group">
<label class="auth-form__label" for="signup-email">Email</label>
<input
class="auth-form__input"
type="email"
id="signup-email"
name="email"
autocomplete="email"
required
/>
</div>
<div class="auth-form__group">
<label class="auth-form__label" for="signup-password">Password</label>
<div class="auth-form__group">
<label class="auth-form__label" for="signup-password">Password</label>
<div class="auth-form__input-wrap">
<input
class="auth-form__input"
type="password"
@@ -80,23 +81,24 @@
/>
</svg>
</button>
<span class="auth-form__hint">(at least 8 characters)</span>
</div>
<div class="auth-form__group">
<label class="auth-form__label" for="signup-confirm">Confirm Password</label>
<input
class="auth-form__input"
type="password"
id="signup-confirm"
name="confirm_password"
autocomplete="new-password"
required
/>
</div>
<span class="auth-form__hint">(at least 8 characters)</span>
</div>
<div class="auth-form__group">
<label class="auth-form__label" for="signup-confirm">Confirm Password</label>
<input
class="auth-form__input"
type="password"
id="signup-confirm"
name="confirm_password"
autocomplete="new-password"
required
/>
</div>
<button type="submit" class="auth-form__submit" id="signup-submit">
<span class="auth-form__submit-text">Create Account</span>
<span class="auth-form__submit-text">Send verification code</span>
<span class="auth-form__submit-loader" style="display: none">
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -114,6 +116,56 @@
</button>
</form>
<!-- ── STEP 2 — OTP verification ──────────────────────────────── -->
<div id="otp-panel" style="display: none">
<p class="otp-panel__hint">
A 6-digit code was sent to <strong id="otp-email-display"></strong>.<br />
Enter it below. The code expires in 10&nbsp;minutes.
</p>
<div class="auth-form" style="margin-top: 1rem">
<div class="auth-form__group">
<label class="auth-form__label" for="otp-input">Verification code</label>
<input
class="auth-form__input otp-panel__input"
type="text"
id="otp-input"
inputmode="numeric"
maxlength="6"
pattern="\d{6}"
placeholder="000000"
autocomplete="one-time-code"
/>
</div>
<button type="button" class="auth-form__submit" id="otp-submit">
<span class="auth-form__submit-text">Verify &amp; create account</span>
<span class="auth-form__submit-loader" style="display: none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="16"
height="16"
fill="currentColor"
class="spin"
>
<path
d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"
/>
</svg>
</span>
</button>
<p class="otp-panel__resend">
Didn't get it?
<button type="button" id="otp-resend" class="otp-panel__resend-btn">
Resend code
</button>
<span id="otp-resend-timer" style="display: none"></span>
</p>
</div>
</div>
<p class="auth-card__footer">
Already have an account? <a href="/login/" class="auth-card__link">Log in</a>
</p>
@@ -124,12 +176,38 @@
(function () {
var BACKEND = 'https://backend.freedoms4.org/auth.php';
var form = document.getElementById('signup-form');
var msgBox = document.getElementById('auth-message');
var submitBtn = document.getElementById('signup-submit');
var pwdInput = document.getElementById('signup-password');
var eyeBtn = form.querySelector('.auth-form__eye');
var state = { username: '', email: '', password: '' };
var msgBox = document.getElementById('auth-message');
var signupForm = document.getElementById('signup-form');
var signupSubmit = document.getElementById('signup-submit');
var otpPanel = document.getElementById('otp-panel');
var otpEmailDisp = document.getElementById('otp-email-display');
var otpInput = document.getElementById('otp-input');
var otpSubmit = document.getElementById('otp-submit');
var otpResendBtn = document.getElementById('otp-resend');
var otpResendTimer = document.getElementById('otp-resend-timer');
var pwdInput = document.getElementById('signup-password');
var eyeBtn = signupForm.querySelector('.auth-form__eye');
function showMsg(text, type) {
msgBox.textContent = text;
msgBox.className = 'auth-message auth-message--' + type;
msgBox.style.display = 'block';
}
function hideMsg() {
msgBox.style.display = 'none';
}
function setLoading(btn, on) {
btn.disabled = on;
btn.querySelector('.auth-form__submit-text').style.display = on ? 'none' : '';
btn.querySelector('.auth-form__submit-loader').style.display = on
? 'inline-flex'
: 'none';
}
// Password eye toggle
eyeBtn.addEventListener('click', function () {
var isText = pwdInput.type === 'text';
pwdInput.type = isText ? 'password' : 'text';
@@ -137,15 +215,30 @@
eyeBtn.querySelector('.eye-hide').style.display = isText ? 'none' : '';
});
function showMsg(text, type) {
msgBox.textContent = text;
msgBox.className = 'auth-message auth-message--' + type;
msgBox.style.display = 'block';
// Resend cooldown
var resendInterval = null;
function startResendCooldown(seconds) {
otpResendBtn.style.display = 'none';
otpResendTimer.style.display = '';
var remaining = seconds;
otpResendTimer.textContent = 'Resend in ' + remaining + 's';
resendInterval = setInterval(function () {
remaining--;
if (remaining <= 0) {
clearInterval(resendInterval);
otpResendTimer.style.display = 'none';
otpResendBtn.style.display = '';
otpResendBtn.disabled = false;
} else {
otpResendTimer.textContent = 'Resend in ' + remaining + 's';
}
}, 1000);
}
form.addEventListener('submit', function (e) {
// STEP 1 — validate fields and send OTP
signupForm.addEventListener('submit', function (e) {
e.preventDefault();
msgBox.style.display = 'none';
hideMsg();
var username = document.getElementById('signup-username').value.trim();
var email = document.getElementById('signup-email').value.trim();
@@ -172,9 +265,82 @@
return;
}
submitBtn.disabled = true;
submitBtn.querySelector('.auth-form__submit-text').style.display = 'none';
submitBtn.querySelector('.auth-form__submit-loader').style.display = 'inline-flex';
state.username = username;
state.email = email;
state.password = password;
setLoading(signupSubmit, true);
fetch(BACKEND, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ action: 'send_otp', email: email }),
})
.then(function (r) {
return r.json();
})
.then(function (data) {
if (data.success) {
signupForm.style.display = 'none';
otpEmailDisp.textContent = email;
otpPanel.style.display = '';
otpInput.value = '';
otpInput.focus();
startResendCooldown(60);
showMsg('Code sent! Check your inbox (and spam folder).', 'success');
} else {
showMsg(data.message || 'Failed to send code. Please try again.', 'error');
}
})
.catch(function () {
showMsg('Network error. Please try again.', 'error');
})
.finally(function () {
setLoading(signupSubmit, false);
});
});
// Resend
otpResendBtn.addEventListener('click', function () {
hideMsg();
otpResendBtn.disabled = true;
fetch(BACKEND, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ action: 'send_otp', email: state.email }),
})
.then(function (r) {
return r.json();
})
.then(function (data) {
if (data.success) {
showMsg('New code sent!', 'success');
startResendCooldown(60);
} else {
showMsg(data.message || 'Could not resend. Please try again.', 'error');
otpResendBtn.disabled = false;
}
})
.catch(function () {
showMsg('Network error. Please try again.', 'error');
otpResendBtn.disabled = false;
});
});
// STEP 2 — verify OTP and create account in one request
function doSignup() {
hideMsg();
var otp = otpInput.value.trim();
if (!/^\d{6}$/.test(otp)) {
showMsg('Please enter the 6-digit code from your email.', 'error');
return;
}
setLoading(otpSubmit, true);
fetch(BACKEND, {
method: 'POST',
@@ -182,9 +348,10 @@
credentials: 'include',
body: JSON.stringify({
action: 'signup',
username: username,
email: email,
password: password,
username: state.username,
email: state.email,
password: state.password,
otp: otp,
}),
})
.then(function (r) {
@@ -195,19 +362,21 @@
showMsg('Account created! Redirecting to login\u2026', 'success');
setTimeout(function () {
window.location.href = '/login/';
}, 1500);
}, 1800);
} else {
showMsg(data.message || 'Sign-up failed. Please try again.', 'error');
setLoading(otpSubmit, false);
}
})
.catch(function () {
showMsg('Network error. Please try again.', 'error');
})
.finally(function () {
submitBtn.disabled = false;
submitBtn.querySelector('.auth-form__submit-text').style.display = '';
submitBtn.querySelector('.auth-form__submit-loader').style.display = 'none';
setLoading(otpSubmit, false);
});
}
otpSubmit.addEventListener('click', doSignup);
otpInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter') doSignup();
});
})();
</script>

View File

@@ -198,3 +198,41 @@
[data-theme='light'] .auth-form__submit {
color: #fff;
}
/* ── OTP panel ───────────────────────────────────────────────────────────── */
.otp-panel__hint {
font-size: 0.87rem;
line-height: 1.5;
color: var(--foreground-color);
margin: 0;
}
.otp-panel__input {
letter-spacing: 0.25em;
font-size: 1.15rem;
text-align: center;
}
.otp-panel__resend {
font-size: 0.82rem;
color: var(--foreground-color3, #888);
text-align: center;
margin: 0;
}
.otp-panel__resend-btn {
background: none;
border: none;
padding: 0;
font: inherit;
font-size: 0.82rem;
color: var(--accent-color);
cursor: pointer;
font-weight: 600;
text-decoration: underline;
}
.otp-panel__resend-btn:hover {
opacity: 0.8;
}

View File

@@ -252,7 +252,6 @@
@media (max-width: 600px) {
/* Profile button: sit above the subscribe button, right-aligned to it */
.brand__actions {
gap: 0.16rem;
position: relative;
}
@@ -470,6 +469,7 @@
cursor: pointer;
line-height: 1;
transition: opacity 0.2s ease;
margin-right: -0.17rem;
}
.theme-toggle:hover {
@@ -552,6 +552,7 @@
height: 2.3rem;
box-sizing: border-box;
padding: 0;
margin-right: -0.5rem;
}
.theme-toggle svg {