adding variviction

This commit is contained in:
Flatlogic Bot 2026-01-25 16:21:07 +00:00
parent 241aa3abd2
commit b77b8b1619
20 changed files with 749 additions and 419 deletions

View File

@ -211,5 +211,6 @@ WHATSAPP_ENABLED = os.getenv("WHATSAPP_ENABLED", "true").lower() == "true"
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'dashboard'
LOGOUT_REDIRECT_URL = 'index'

View File

@ -15,6 +15,7 @@ class UserRegistrationForm(forms.ModelForm):
password_confirm = forms.CharField(widget=forms.PasswordInput, label=_("Confirm Password"))
role = forms.ChoiceField(choices=Profile.ROLE_CHOICES, label=_("Register as"))
phone_number = forms.CharField(max_length=20, label=_("Phone Number"))
verification_method = forms.ChoiceField(choices=[('email', _('Email')), ('whatsapp', _('WhatsApp'))], label=_("Verify via"), widget=forms.RadioSelect, initial='email')
country = forms.ModelChoiceField(queryset=Country.objects.all(), required=False, label=_("Country"))
governate = forms.ModelChoiceField(queryset=Governate.objects.none(), required=False, label=_("Governate"))

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-01-25 15:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0013_platformprofile_enable_payment'),
]
operations = [
migrations.AlterField(
model_name='otpverification',
name='purpose',
field=models.CharField(choices=[('profile_update', 'Profile Update'), ('password_reset', 'Password Reset'), ('registration', 'Registration')], default='profile_update', max_length=20),
),
]

View File

@ -198,6 +198,7 @@ class OTPVerification(models.Model):
PURPOSE_CHOICES = (
('profile_update', _('Profile Update')),
('password_reset', _('Password Reset')),
('registration', _('Registration')),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
code = models.CharField(max_length=6)

View File

@ -8,6 +8,13 @@
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<!-- Back to Profile -->
<div class="mb-4">
<a href="{% url 'profile' %}" class="btn btn-link text-decoration-none text-muted ps-0">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Profile" %}
</a>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-5">
<h2 class="fw-bold mb-4 text-center">{% trans "Edit Profile" %}</h2>
@ -91,4 +98,4 @@ document.addEventListener('DOMContentLoaded', function() {
border-radius: 8px;
}
</style>
{% endblock %}
{% endblock %}

View File

@ -8,6 +8,13 @@
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-5">
<!-- Back to Home -->
<div class="mb-4">
<a href="{% url 'index' %}" class="btn btn-link text-decoration-none text-muted ps-0">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Home" %}
</a>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-5">
<h2 class="fw-bold mb-4 text-center">{% trans "Login to masarX" %}</h2>
@ -47,4 +54,4 @@
border-radius: 8px;
}
</style>
{% endblock %}
{% endblock %}

View File

@ -7,24 +7,33 @@
<section class="py-5 bg-light" style="min-height: 80vh;">
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="col-md-10 col-lg-8">
<!-- Back to Home -->
<div class="mb-4">
<a href="{% url 'index' %}" class="btn btn-link text-decoration-none text-muted ps-0">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Home" %}
</a>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-5">
<h2 class="fw-bold mb-4 text-center">{% trans "Join masarX" %}</h2>
<form method="post" id="registrationForm">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label class="form-label fw-semibold" for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<div class="form-text small">{{ field.help_text }}</div>
{% endif %}
{% if field.errors %}
<div class="text-danger small">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}
<div class="row">
{% for field in form %}
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold" for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<div class="form-text small">{{ field.help_text }}</div>
{% endif %}
{% if field.errors %}
<div class="text-danger small">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-masarx-primary w-100 py-2 mt-3">{% trans "Create Account" %}</button>
</form>
<div class="text-center mt-4">
@ -43,47 +52,50 @@ document.addEventListener('DOMContentLoaded', function() {
const governateSelect = document.getElementById('id_governate');
const citySelect = document.getElementById('id_city');
countrySelect.addEventListener('change', function() {
const countryId = this.value;
governateSelect.innerHTML = '<option value="">{% trans "Select Governate" %}</option>';
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
if (countryId) {
fetch(`{% url 'get_governates' %}?country_id=${countryId}`)
.then(response => response.json())
.then(data => {
data.forEach(gov => {
const option = document.createElement('option');
option.value = gov.id;
option.textContent = gov.name;
governateSelect.appendChild(option);
// Only attach listeners if elements exist (safety check)
if (countrySelect && governateSelect && citySelect) {
countrySelect.addEventListener('change', function() {
const countryId = this.value;
governateSelect.innerHTML = '<option value="">{% trans "Select Governate" %}</option>';
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
if (countryId) {
fetch(`{% url 'get_governates' %}?country_id=${countryId}`)
.then(response => response.json())
.then(data => {
data.forEach(gov => {
const option = document.createElement('option');
option.value = gov.id;
option.textContent = gov.name;
governateSelect.appendChild(option);
});
});
});
}
});
}
});
governateSelect.addEventListener('change', function() {
const governateId = this.value;
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
if (governateId) {
fetch(`{% url 'get_cities' %}?governate_id=${governateId}`)
.then(response => response.json())
.then(data => {
data.forEach(city => {
const option = document.createElement('option');
option.value = city.id;
option.textContent = city.name;
citySelect.appendChild(option);
governateSelect.addEventListener('change', function() {
const governateId = this.value;
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
if (governateId) {
fetch(`{% url 'get_cities' %}?governate_id=${governateId}`)
.then(response => response.json())
.then(data => {
data.forEach(city => {
const option = document.createElement('option');
option.value = city.id;
option.textContent = city.name;
citySelect.appendChild(option);
});
});
});
}
});
}
});
}
});
</script>
<style>
input, select {
input:not([type=radio]):not([type=checkbox]), select {
display: block;
width: 100%;
padding: 0.375rem 0.75rem;
@ -98,6 +110,17 @@ document.addEventListener('DOMContentLoaded', function() {
border-radius: 8px;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
#id_verification_method {
list-style: none;
padding-left: 0;
display: flex;
gap: 1rem;
}
#id_verification_method li {
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn-masarx-primary {
background-color: var(--accent-orange);
border-color: var(--accent-orange);
@ -106,4 +129,4 @@ document.addEventListener('DOMContentLoaded', function() {
border-radius: 8px;
}
</style>
{% endblock %}
{% endblock %}

View File

@ -6,6 +6,13 @@
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<!-- Back to Dashboard -->
<div class="mb-4">
<a href="{% url 'dashboard' %}" class="btn btn-link text-decoration-none text-muted ps-0">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Dashboard" %}
</a>
</div>
<div class="card border-0 shadow-sm p-4" style="border-radius: 20px;">
<h2 class="mb-4">{% trans "Request a Shipment" %}</h2>
<form method="POST">

View File

@ -8,6 +8,13 @@
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<!-- Back to Edit Profile -->
<div class="mb-4">
<a href="{% url 'edit_profile' %}" class="btn btn-link text-decoration-none text-muted ps-0">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Edit Profile" %}
</a>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-5">
<h2 class="fw-bold mb-4 text-center">{% trans "Verification Required" %}</h2>
@ -44,4 +51,4 @@
letter-spacing: 0.5em;
}
</style>
{% endblock %}
{% endblock %}

View File

@ -0,0 +1,53 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Verify Account" %} | masarX{% endblock %}
{% block content %}
<section class="py-5 bg-light" style="min-height: 80vh;">
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<!-- Back to Register -->
<div class="mb-4">
<a href="{% url 'register' %}" class="btn btn-link text-decoration-none text-muted ps-0">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Register" %}
</a>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-5">
<h2 class="fw-bold mb-4 text-center">{% trans "Account Verification" %}</h2>
<p class="text-center text-muted mb-4">
{% trans "We have sent a verification code to verify your account. Please enter it below to complete registration." %}
</p>
<form method="post">
{% csrf_token %}
<div class="mb-4">
<label class="form-label fw-semibold" for="code">{% trans "Verification Code" %}</label>
<input type="text" name="code" id="code" class="form-control text-center fs-3 tracking-widest" maxlength="6" required placeholder="000000">
</div>
<div class="d-grid">
<button type="submit" class="btn btn-masarx-primary py-2">{% trans "Verify Account" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
<style>
.btn-masarx-primary {
background-color: var(--accent-orange);
border-color: var(--accent-orange);
color: white;
font-weight: 600;
border-radius: 8px;
}
.tracking-widest {
letter-spacing: 0.5em;
}
</style>
{% endblock %}

View File

@ -7,6 +7,7 @@ urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='core/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
path('register/', views.register, name='register'),
path('register/verify/', views.verify_registration, name='verify_registration'),
path('dashboard/', views.dashboard, name='dashboard'),
path('shipment-request/', views.shipment_request, name='shipment_request'),
path('accept-parcel/<int:parcel_id>/', views.accept_parcel, name='accept_parcel'),

View File

@ -43,13 +43,76 @@ def register(request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect('dashboard')
# Save user but inactive
user = form.save(commit=True)
user.is_active = False
user.save()
# Generate OTP
code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=user, code=code, purpose='registration')
# Send OTP
method = form.cleaned_data.get('verification_method', 'email')
if method == 'whatsapp':
phone = user.profile.phone_number
send_whatsapp_message(phone, f"Your verification code is: {code}")
messages.info(request, _("Verification code sent to WhatsApp."))
else:
send_mail(
_('Verification Code'),
f'Your verification code is: {code}',
settings.DEFAULT_FROM_EMAIL,
[user.email],
fail_silently=False,
)
messages.info(request, _("Verification code sent to email."))
request.session['registration_user_id'] = user.id
return redirect('verify_registration')
else:
form = UserRegistrationForm()
return render(request, 'core/register.html', {'form': form})
def verify_registration(request):
if 'registration_user_id' not in request.session:
messages.error(request, _("Session expired or invalid."))
return redirect('register')
if request.method == 'POST':
code = request.POST.get('code')
user_id = request.session['registration_user_id']
try:
user = User.objects.get(id=user_id)
otp = OTPVerification.objects.filter(
user=user,
code=code,
purpose='registration',
is_verified=False
).latest('created_at')
if otp.is_valid():
# Activate User
user.is_active = True
user.save()
# Cleanup
otp.is_verified = True
otp.save()
del request.session['registration_user_id']
# Login
login(request, user)
messages.success(request, _("Account verified successfully!"))
return redirect('dashboard')
else:
messages.error(request, _("Invalid or expired code."))
except (User.DoesNotExist, OTPVerification.DoesNotExist):
messages.error(request, _("Invalid code."))
return render(request, 'core/verify_registration.html')
@login_required
def dashboard(request):
# Ensure profile exists

Binary file not shown.

File diff suppressed because it is too large Load Diff