270 lines
13 KiB
HTML
270 lines
13 KiB
HTML
{% extends 'base.html' %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Login" %} | masarX{% endblock %}
|
|
|
|
{% block content %}
|
|
<section class="py-5 bg-light" style="min-height: 80vh; display: flex; align-items: center;">
|
|
<div class="container">
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-6 col-lg-5">
|
|
<div class="card shadow-sm border-0 rounded-4">
|
|
<div class="card-body p-4 p-md-5">
|
|
|
|
<!-- Logo or Brand Name -->
|
|
<div class="text-center mb-4">
|
|
{% if platform_profile.logo %}
|
|
<img src="{{ platform_profile.logo.url }}" alt="{{ platform_profile.name }}" height="70" class="mb-3">
|
|
{% else %}
|
|
<h2 class="fw-bold text-masarx-primary mb-3">{{ platform_profile.name|default:"masarX" }}</h2>
|
|
{% endif %}
|
|
<h4 class="fw-bold">{% trans "Welcome Back" %}</h4>
|
|
<p class="text-muted small">{% trans "Please login to your account" %}</p>
|
|
</div>
|
|
|
|
<!-- Login Method Tabs -->
|
|
<ul class="nav nav-pills nav-fill mb-4 p-1 bg-light rounded-3" id="loginTab" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active fw-bold rounded-3" id="password-tab" data-bs-toggle="tab" data-bs-target="#password-login" type="button" role="tab" aria-controls="password-login" aria-selected="true">{% trans "Password" %}</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link fw-bold rounded-3" id="otp-tab" data-bs-toggle="tab" data-bs-target="#otp-login" type="button" role="tab" aria-controls="otp-login" aria-selected="false">{% trans "OTP Login" %}</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content" id="loginTabContent">
|
|
<!-- Password Login Tab -->
|
|
<div class="tab-pane fade show active" id="password-login" role="tabpanel" aria-labelledby="password-tab">
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
{% for field in form %}
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ field.label }}</label>
|
|
{{ field }}
|
|
{% if field.errors %}
|
|
<div class="text-danger small mt-1">{{ field.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div class="form-check">
|
|
<!-- Optional: Remember Me logic could go here later -->
|
|
</div>
|
|
<a href="{% url 'password_reset' %}" class="text-decoration-none text-muted small hover-orange">
|
|
{% trans "Forgot Password?" %}
|
|
</a>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-masarx-primary w-100 py-2 fw-bold mb-3">{% trans "Login" %}</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- OTP Login Tab -->
|
|
<div class="tab-pane fade" id="otp-login" role="tabpanel" aria-labelledby="otp-tab">
|
|
<div id="otp-step-1">
|
|
<div class="mb-3">
|
|
<label for="otp-identifier" class="form-label fw-medium">{% trans "Email or Phone Number" %}</label>
|
|
<input type="text" class="form-control" id="otp-identifier" placeholder="{% trans 'e.g. user@example.com or 96812345678' %}">
|
|
<div id="otp-identifier-error" class="text-danger small mt-1 d-none"></div>
|
|
</div>
|
|
<button type="button" class="btn btn-masarx-primary w-100 py-2 fw-bold mb-3" id="btn-send-otp">
|
|
<span id="btn-send-otp-text">{% trans "Send OTP" %}</span>
|
|
<span id="btn-send-otp-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="otp-step-2" class="d-none">
|
|
<div class="alert alert-info py-2 small" id="otp-sent-msg"></div>
|
|
<div class="mb-3">
|
|
<label for="otp-code" class="form-label fw-medium">{% trans "Enter OTP Code" %}</label>
|
|
<input type="text" class="form-control text-center letter-spacing-2" id="otp-code" placeholder="123456" maxlength="6">
|
|
<div id="otp-code-error" class="text-danger small mt-1 d-none"></div>
|
|
</div>
|
|
<button type="button" class="btn btn-masarx-primary w-100 py-2 fw-bold mb-3" id="btn-verify-otp">
|
|
<span id="btn-verify-otp-text">{% trans "Verify & Login" %}</span>
|
|
<span id="btn-verify-otp-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
|
</button>
|
|
<div class="text-center">
|
|
<button type="button" class="btn btn-link text-muted small text-decoration-none" id="btn-back-otp">{% trans "Back" %}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-center border-top pt-3 mt-2">
|
|
<span class="text-muted small">{% trans "Don't have an account?" %}</span>
|
|
<a href="{% url 'register' %}" class="text-masarx-orange text-decoration-none fw-bold ms-1">{% trans "Register" %}</a>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
.nav-pills .nav-link {
|
|
color: #6c757d;
|
|
border-radius: 8px;
|
|
padding: 10px 15px;
|
|
}
|
|
.nav-pills .nav-link.active {
|
|
background-color: var(--accent-orange);
|
|
color: white;
|
|
}
|
|
.form-control {
|
|
border-radius: 8px;
|
|
padding: 12px 15px;
|
|
border-color: #dee2e6;
|
|
}
|
|
.form-control:focus {
|
|
border-color: var(--accent-orange);
|
|
box-shadow: 0 0 0 0.25rem rgba(255, 126, 21, 0.15);
|
|
}
|
|
.btn-masarx-primary {
|
|
background-color: var(--accent-orange);
|
|
border-color: var(--accent-orange);
|
|
color: white;
|
|
border-radius: 8px;
|
|
transition: all 0.3s;
|
|
}
|
|
.btn-masarx-primary:hover {
|
|
background-color: #e66a00;
|
|
border-color: #e66a00;
|
|
color: white;
|
|
}
|
|
.text-masarx-orange {
|
|
color: var(--accent-orange);
|
|
}
|
|
.hover-orange:hover {
|
|
color: var(--accent-orange) !important;
|
|
}
|
|
.letter-spacing-2 {
|
|
letter-spacing: 4px;
|
|
font-size: 1.2rem;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const btnSendOtp = document.getElementById('btn-send-otp');
|
|
const btnVerifyOtp = document.getElementById('btn-verify-otp');
|
|
const btnBackOtp = document.getElementById('btn-back-otp');
|
|
const otpStep1 = document.getElementById('otp-step-1');
|
|
const otpStep2 = document.getElementById('otp-step-2');
|
|
const inputIdentifier = document.getElementById('otp-identifier');
|
|
const inputCode = document.getElementById('otp-code');
|
|
const errorIdentifier = document.getElementById('otp-identifier-error');
|
|
const errorCode = document.getElementById('otp-code-error');
|
|
const msgSent = document.getElementById('otp-sent-msg');
|
|
|
|
let userId = null;
|
|
|
|
function showLoading(btnId, show) {
|
|
const btn = document.getElementById(btnId);
|
|
const textSpan = document.getElementById(btnId + '-text');
|
|
const spinnerSpan = document.getElementById(btnId + '-spinner');
|
|
|
|
if (show) {
|
|
btn.disabled = true;
|
|
textSpan.classList.add('d-none');
|
|
spinnerSpan.classList.remove('d-none');
|
|
} else {
|
|
btn.disabled = false;
|
|
textSpan.classList.remove('d-none');
|
|
spinnerSpan.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function showError(element, message) {
|
|
element.innerText = message;
|
|
element.classList.remove('d-none');
|
|
}
|
|
|
|
function clearErrors() {
|
|
errorIdentifier.classList.add('d-none');
|
|
errorCode.classList.add('d-none');
|
|
}
|
|
|
|
btnSendOtp.addEventListener('click', function() {
|
|
const identifier = inputIdentifier.value.trim();
|
|
if (!identifier) {
|
|
showError(errorIdentifier, "{% trans 'Please enter your email or phone number.' %}");
|
|
return;
|
|
}
|
|
|
|
clearErrors();
|
|
showLoading('btn-send-otp', true);
|
|
|
|
fetch("{% url 'request_login_otp' %}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-CSRFToken': '{{ csrf_token }}'
|
|
},
|
|
body: 'identifier=' + encodeURIComponent(identifier)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
showLoading('btn-send-otp', false);
|
|
if (data.success) {
|
|
userId = data.user_id;
|
|
msgSent.innerText = data.message;
|
|
otpStep1.classList.add('d-none');
|
|
otpStep2.classList.remove('d-none');
|
|
} else {
|
|
showError(errorIdentifier, data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showLoading('btn-send-otp', false);
|
|
showError(errorIdentifier, "{% trans 'An error occurred. Please try again.' %}");
|
|
console.error('Error:', error);
|
|
});
|
|
});
|
|
|
|
btnVerifyOtp.addEventListener('click', function() {
|
|
const code = inputCode.value.trim();
|
|
if (!code) {
|
|
showError(errorCode, "{% trans 'Please enter the code.' %}");
|
|
return;
|
|
}
|
|
|
|
clearErrors();
|
|
showLoading('btn-verify-otp', true);
|
|
|
|
fetch("{% url 'verify_login_otp' %}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-CSRFToken': '{{ csrf_token }}'
|
|
},
|
|
body: 'user_id=' + encodeURIComponent(userId) + '&code=' + encodeURIComponent(code)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
showLoading('btn-verify-otp', false);
|
|
if (data.success) {
|
|
window.location.href = data.redirect_url;
|
|
} else {
|
|
showError(errorCode, data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showLoading('btn-verify-otp', false);
|
|
showError(errorCode, "{% trans 'An error occurred. Please try again.' %}");
|
|
console.error('Error:', error);
|
|
});
|
|
});
|
|
|
|
btnBackOtp.addEventListener('click', function() {
|
|
otpStep2.classList.add('d-none');
|
|
otpStep1.classList.remove('d-none');
|
|
inputCode.value = '';
|
|
clearErrors();
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |