mirror of
https://github.com/hyzendust/hyzendust.github.io.git
synced 2026-06-30 23:12:16 +02:00
Compare commits
8 Commits
74fb5d0491
...
b05f1a731d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b05f1a731d | ||
|
|
28a6e82bfc | ||
|
|
00b91c13da | ||
|
|
282ce882f8 | ||
|
|
62573b2893 | ||
|
|
55b2921ecf | ||
|
|
d0208d7d1a | ||
|
|
f758fc8bac |
@@ -198,3 +198,41 @@
|
|||||||
[data-theme='light'] .auth-form__submit {
|
[data-theme='light'] .auth-form__submit {
|
||||||
color: #fff;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -252,7 +252,6 @@
|
|||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
/* Profile button: sit above the subscribe button, right-aligned to it */
|
/* Profile button: sit above the subscribe button, right-aligned to it */
|
||||||
.brand__actions {
|
.brand__actions {
|
||||||
gap: 0.16rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,6 +469,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
|
margin-right: -0.17rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:hover {
|
.theme-toggle:hover {
|
||||||
@@ -552,6 +552,7 @@
|
|||||||
height: 2.3rem;
|
height: 2.3rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin-right: -0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle svg {
|
.theme-toggle svg {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
|
|||||||
<div class="auth-card">
|
<div class="auth-card">
|
||||||
<div id="auth-message" class="auth-message" aria-live="polite" style="display: none"></div>
|
<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>
|
<form id="signup-form" class="auth-form" novalidate>
|
||||||
<div class="auth-form__input-wrap">
|
|
||||||
<div class="auth-form__group">
|
<div class="auth-form__group">
|
||||||
<label class="auth-form__label" for="signup-username">Username</label>
|
<label class="auth-form__label" for="signup-username">Username</label>
|
||||||
<input
|
<input
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
<div class="auth-form__group">
|
<div class="auth-form__group">
|
||||||
<label class="auth-form__label" for="signup-password">Password</label>
|
<label class="auth-form__label" for="signup-password">Password</label>
|
||||||
|
<div class="auth-form__input-wrap">
|
||||||
<input
|
<input
|
||||||
class="auth-form__input"
|
class="auth-form__input"
|
||||||
type="password"
|
type="password"
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<span class="auth-form__hint">(at least 8 characters)</span>
|
<span class="auth-form__hint">(at least 8 characters)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -94,9 +96,9 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<button type="submit" class="auth-form__submit" id="signup-submit">
|
<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">
|
<span class="auth-form__submit-loader" style="display: none">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -114,6 +116,56 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</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 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 & 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">
|
<p class="auth-card__footer">
|
||||||
Already have an account? <a href="/login/" class="auth-card__link">Log in</a>
|
Already have an account? <a href="/login/" class="auth-card__link">Log in</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -124,12 +176,38 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var BACKEND = 'https://backend.freedoms4.org/auth.php';
|
var BACKEND = 'https://backend.freedoms4.org/auth.php';
|
||||||
|
|
||||||
var form = document.getElementById('signup-form');
|
var state = { username: '', email: '', password: '' };
|
||||||
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 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 () {
|
eyeBtn.addEventListener('click', function () {
|
||||||
var isText = pwdInput.type === 'text';
|
var isText = pwdInput.type === 'text';
|
||||||
pwdInput.type = isText ? 'password' : 'text';
|
pwdInput.type = isText ? 'password' : 'text';
|
||||||
@@ -137,15 +215,30 @@
|
|||||||
eyeBtn.querySelector('.eye-hide').style.display = isText ? 'none' : '';
|
eyeBtn.querySelector('.eye-hide').style.display = isText ? 'none' : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
function showMsg(text, type) {
|
// Resend cooldown
|
||||||
msgBox.textContent = text;
|
var resendInterval = null;
|
||||||
msgBox.className = 'auth-message auth-message--' + type;
|
function startResendCooldown(seconds) {
|
||||||
msgBox.style.display = 'block';
|
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();
|
e.preventDefault();
|
||||||
msgBox.style.display = 'none';
|
hideMsg();
|
||||||
|
|
||||||
var username = document.getElementById('signup-username').value.trim();
|
var username = document.getElementById('signup-username').value.trim();
|
||||||
var email = document.getElementById('signup-email').value.trim();
|
var email = document.getElementById('signup-email').value.trim();
|
||||||
@@ -172,9 +265,82 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
submitBtn.disabled = true;
|
state.username = username;
|
||||||
submitBtn.querySelector('.auth-form__submit-text').style.display = 'none';
|
state.email = email;
|
||||||
submitBtn.querySelector('.auth-form__submit-loader').style.display = 'inline-flex';
|
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, {
|
fetch(BACKEND, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -182,9 +348,10 @@
|
|||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
action: 'signup',
|
action: 'signup',
|
||||||
username: username,
|
username: state.username,
|
||||||
email: email,
|
email: state.email,
|
||||||
password: password,
|
password: state.password,
|
||||||
|
otp: otp,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(function (r) {
|
.then(function (r) {
|
||||||
@@ -195,19 +362,21 @@
|
|||||||
showMsg('Account created! Redirecting to login\u2026', 'success');
|
showMsg('Account created! Redirecting to login\u2026', 'success');
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
window.location.href = '/login/';
|
window.location.href = '/login/';
|
||||||
}, 1500);
|
}, 1800);
|
||||||
} else {
|
} else {
|
||||||
showMsg(data.message || 'Sign-up failed. Please try again.', 'error');
|
showMsg(data.message || 'Sign-up failed. Please try again.', 'error');
|
||||||
|
setLoading(otpSubmit, false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
showMsg('Network error. Please try again.', 'error');
|
showMsg('Network error. Please try again.', 'error');
|
||||||
})
|
setLoading(otpSubmit, false);
|
||||||
.finally(function () {
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
submitBtn.querySelector('.auth-form__submit-text').style.display = '';
|
|
||||||
submitBtn.querySelector('.auth-form__submit-loader').style.display = 'none';
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
otpSubmit.addEventListener('click', doSignup);
|
||||||
|
otpInput.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Enter') doSignup();
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -198,3 +198,41 @@
|
|||||||
[data-theme='light'] .auth-form__submit {
|
[data-theme='light'] .auth-form__submit {
|
||||||
color: #fff;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -252,7 +252,6 @@
|
|||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
/* Profile button: sit above the subscribe button, right-aligned to it */
|
/* Profile button: sit above the subscribe button, right-aligned to it */
|
||||||
.brand__actions {
|
.brand__actions {
|
||||||
gap: 0.16rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,6 +469,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
|
margin-right: -0.17rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:hover {
|
.theme-toggle:hover {
|
||||||
@@ -552,6 +552,7 @@
|
|||||||
height: 2.3rem;
|
height: 2.3rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin-right: -0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle svg {
|
.theme-toggle svg {
|
||||||
|
|||||||
Reference in New Issue
Block a user