Autosave: 20260126-043309
This commit is contained in:
parent
7e8ed2b3cb
commit
a3174399c8
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -149,6 +149,10 @@ class PlatformProfileAdmin(admin.ModelAdmin):
|
||||
fieldsets += ((_('Tools'), {'fields': ('test_connection_link',)}),)
|
||||
return fieldsets
|
||||
|
||||
class CountryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name_en', 'name_ar', 'phone_code')
|
||||
search_fields = ('name_en', 'name_ar', 'phone_code')
|
||||
|
||||
class TestimonialAdmin(admin.ModelAdmin):
|
||||
list_display = ('name_en', 'role_en', 'is_active', 'created_at')
|
||||
list_filter = ('is_active', 'created_at')
|
||||
@ -158,7 +162,7 @@ class TestimonialAdmin(admin.ModelAdmin):
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, CustomUserAdmin)
|
||||
admin.site.register(Parcel, ParcelAdmin)
|
||||
admin.site.register(Country)
|
||||
admin.site.register(Country, CountryAdmin)
|
||||
admin.site.register(Governate)
|
||||
admin.site.register(City)
|
||||
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
||||
|
||||
@ -14,7 +14,10 @@ class UserRegistrationForm(forms.ModelForm):
|
||||
password = forms.CharField(widget=forms.PasswordInput, label=_("Password"))
|
||||
password_confirm = forms.CharField(widget=forms.PasswordInput, label=_("Confirm Password"))
|
||||
role = forms.ChoiceField(choices=Profile.ROLE_CHOICES, label=_("Register as"))
|
||||
|
||||
phone_code = forms.ModelChoiceField(queryset=Country.objects.none(), label=_("Code"), required=False, widget=forms.Select(attrs={'class': 'form-control'}))
|
||||
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"))
|
||||
@ -36,12 +39,17 @@ class UserRegistrationForm(forms.ModelForm):
|
||||
lang = get_language()
|
||||
name_field = 'name_ar' if lang == 'ar' else 'name_en'
|
||||
|
||||
# Phone Code setup
|
||||
self.fields['phone_code'].queryset = Country.objects.exclude(phone_code='').order_by(name_field)
|
||||
self.fields['phone_code'].label_from_instance = lambda obj: f"{obj.phone_code} ({obj.name})"
|
||||
|
||||
self.fields['country'].queryset = Country.objects.all().order_by(name_field)
|
||||
|
||||
# Default Country logic
|
||||
oman = Country.objects.filter(name_en='Oman').first()
|
||||
if oman:
|
||||
self.fields['country'].initial = oman
|
||||
self.fields['phone_code'].initial = oman
|
||||
|
||||
if 'country' in self.data:
|
||||
try:
|
||||
@ -70,6 +78,17 @@ class UserRegistrationForm(forms.ModelForm):
|
||||
raise forms.ValidationError(_("Passwords don't match"))
|
||||
return password_confirm
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
phone_code = cleaned_data.get('phone_code')
|
||||
phone_number = cleaned_data.get('phone_number')
|
||||
|
||||
if phone_code and phone_number:
|
||||
# If user didn't type the code in the phone number input, prepend it
|
||||
if not phone_number.startswith(phone_code.phone_code):
|
||||
cleaned_data['phone_number'] = f"{phone_code.phone_code}{phone_number}"
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=False)
|
||||
user.set_password(self.cleaned_data['password'])
|
||||
@ -90,7 +109,9 @@ class UserProfileForm(forms.ModelForm):
|
||||
last_name = forms.CharField(label=_("Last Name"), max_length=150, widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
email = forms.EmailField(label=_("Email"), widget=forms.EmailInput(attrs={'class': 'form-control'}))
|
||||
|
||||
phone_code = forms.ModelChoiceField(queryset=Country.objects.none(), label=_("Code"), required=False, widget=forms.Select(attrs={'class': 'form-control'}))
|
||||
phone_number = forms.CharField(label=_("Phone Number"), max_length=20, widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
|
||||
address = forms.CharField(label=_("Address"), required=False, widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
profile_picture = forms.ImageField(label=_("Profile Picture"), required=False, widget=forms.FileInput(attrs={'class': 'form-control'}))
|
||||
|
||||
@ -125,10 +146,25 @@ class UserProfileForm(forms.ModelForm):
|
||||
lang = get_language()
|
||||
name_field = 'name_ar' if lang == 'ar' else 'name_en'
|
||||
|
||||
self.fields['country'].queryset = Country.objects.all().order_by(name_field)
|
||||
# Phone Code setup
|
||||
self.fields['phone_code'].queryset = Country.objects.exclude(phone_code='').order_by(name_field)
|
||||
self.fields['phone_code'].label_from_instance = lambda obj: f"{obj.phone_code} ({obj.name})"
|
||||
|
||||
# Default Country logic (Oman)
|
||||
oman = Country.objects.filter(name_en='Oman').first()
|
||||
if oman:
|
||||
self.fields['phone_code'].initial = oman
|
||||
|
||||
# Initial splitting of phone number
|
||||
if self.instance.pk and self.instance.phone_number:
|
||||
for country in Country.objects.exclude(phone_code=''):
|
||||
if self.instance.phone_number.startswith(country.phone_code):
|
||||
self.fields['phone_code'].initial = country
|
||||
# Strip code from display
|
||||
self.fields['phone_number'].initial = self.instance.phone_number[len(country.phone_code):]
|
||||
break
|
||||
|
||||
self.fields['country'].queryset = Country.objects.all().order_by(name_field)
|
||||
|
||||
# Initial QS setup
|
||||
self.fields['governate'].queryset = Governate.objects.none()
|
||||
@ -154,7 +190,19 @@ class UserProfileForm(forms.ModelForm):
|
||||
elif self.instance.pk and self.instance.governate:
|
||||
self.fields['city'].queryset = self.instance.governate.city_set.order_by(name_field)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
phone_code = cleaned_data.get('phone_code')
|
||||
phone_number = cleaned_data.get('phone_number')
|
||||
|
||||
if phone_code and phone_number:
|
||||
if not phone_number.startswith(phone_code.phone_code):
|
||||
cleaned_data['phone_number'] = f"{phone_code.phone_code}{phone_number}"
|
||||
return cleaned_data
|
||||
|
||||
class ParcelForm(forms.ModelForm):
|
||||
receiver_phone_code = forms.ModelChoiceField(queryset=Country.objects.none(), label=_("Receiver Code"), required=False, widget=forms.Select(attrs={'class': 'form-control'}))
|
||||
|
||||
class Meta:
|
||||
model = Parcel
|
||||
fields = [
|
||||
@ -202,16 +250,29 @@ class ParcelForm(forms.ModelForm):
|
||||
lang = get_language()
|
||||
name_field = 'name_ar' if lang == 'ar' else 'name_en'
|
||||
|
||||
# Set querysets for countries
|
||||
self.fields['pickup_country'].queryset = Country.objects.all().order_by(name_field)
|
||||
self.fields['delivery_country'].queryset = Country.objects.all().order_by(name_field)
|
||||
# Phone Code setup
|
||||
self.fields['receiver_phone_code'].queryset = Country.objects.exclude(phone_code='').order_by(name_field)
|
||||
self.fields['receiver_phone_code'].label_from_instance = lambda obj: f"{obj.phone_code} ({obj.name})"
|
||||
|
||||
# Default Country logic
|
||||
oman = Country.objects.filter(name_en='Oman').first()
|
||||
if oman:
|
||||
self.fields['receiver_phone_code'].initial = oman
|
||||
self.fields['pickup_country'].initial = oman
|
||||
self.fields['delivery_country'].initial = oman
|
||||
|
||||
# Initial splitting of phone number (if editing)
|
||||
if self.instance.pk and self.instance.receiver_phone:
|
||||
for country in Country.objects.exclude(phone_code=''):
|
||||
if self.instance.receiver_phone.startswith(country.phone_code):
|
||||
self.fields['receiver_phone_code'].initial = country
|
||||
self.fields['receiver_phone'].initial = self.instance.receiver_phone[len(country.phone_code):]
|
||||
break
|
||||
|
||||
# Set querysets for countries
|
||||
self.fields['pickup_country'].queryset = Country.objects.all().order_by(name_field)
|
||||
self.fields['delivery_country'].queryset = Country.objects.all().order_by(name_field)
|
||||
|
||||
# Pickup
|
||||
self.fields['pickup_governate'].queryset = Governate.objects.none()
|
||||
self.fields['pickup_city'].queryset = City.objects.none()
|
||||
@ -251,3 +312,13 @@ class ParcelForm(forms.ModelForm):
|
||||
self.fields['delivery_city'].queryset = City.objects.filter(governate_id=gov_id).order_by(name_field)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
phone_code = cleaned_data.get('receiver_phone_code')
|
||||
phone_number = cleaned_data.get('receiver_phone')
|
||||
|
||||
if phone_code and phone_number:
|
||||
if not phone_number.startswith(phone_code.phone_code):
|
||||
cleaned_data['receiver_phone'] = f"{phone_code.phone_code}{phone_number}"
|
||||
return cleaned_data
|
||||
18
core/migrations/0016_country_phone_code.py
Normal file
18
core/migrations/0016_country_phone_code.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-25 17:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0015_testimonial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='country',
|
||||
name='phone_code',
|
||||
field=models.CharField(blank=True, help_text='e.g. +968', max_length=10, verbose_name='Phone Code'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -11,6 +11,7 @@ import uuid
|
||||
class Country(models.Model):
|
||||
name_en = models.CharField(_('Name (English)'), max_length=100)
|
||||
name_ar = models.CharField(_('Name (Arabic)'), max_length=100)
|
||||
phone_code = models.CharField(_('Phone Code'), max_length=10, blank=True, help_text=_("e.g. +968"))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -19,7 +20,7 @@ class Country(models.Model):
|
||||
return self.name_en
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return f"{self.name} ({self.phone_code})" if self.phone_code else self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Country')
|
||||
@ -240,4 +241,4 @@ class Testimonial(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _('Testimonial')
|
||||
verbose_name_plural = _('Testimonials')
|
||||
ordering = ['-created_at']
|
||||
ordering = ['-created_at']
|
||||
93
core/templates/core/emails/base_email.html
Normal file
93
core/templates/core/emails/base_email.html
Normal file
@ -0,0 +1,93 @@
|
||||
{% load i18n core_tags %}
|
||||
{% get_platform_profile as platform %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: #f4f6f8;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #333333;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.header {
|
||||
background-color: #ffffff;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
.logo {
|
||||
max-height: 60px;
|
||||
width: auto;
|
||||
}
|
||||
.content {
|
||||
padding: 40px 30px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #888888;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #e67e22; /* Orange accent */
|
||||
color: #ffffff !important;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 {
|
||||
color: #333333;
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 0;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.link-text {
|
||||
color: #e67e22;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
{% if platform and platform.logo %}
|
||||
<img src="{{ protocol }}://{{ domain }}{{ platform.logo.url }}" alt="{{ platform.name }}" class="logo">
|
||||
{% else %}
|
||||
<h2>{{ site_name }}</h2>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© {% now "Y" %} {{ site_name }}. {% trans "All rights reserved." %}</p>
|
||||
{% if platform and platform.address %}
|
||||
<p>{{ platform.address }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
26
core/templates/core/emails/password_reset_email.html
Normal file
26
core/templates/core/emails/password_reset_email.html
Normal file
@ -0,0 +1,26 @@
|
||||
{% extends "core/emails/base_email.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Reset Your Password" %}</h1>
|
||||
|
||||
<p>{% trans "Hello," %}</p>
|
||||
|
||||
<p>{% trans "You are receiving this email because you requested a password reset for your account at" %} <strong>{{ site_name }}</strong>.</p>
|
||||
|
||||
<p>{% trans "Please click the button below to choose a new password:" %}</p>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<a href="{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}" class="button">{% trans "Reset Password" %}</a>
|
||||
</div>
|
||||
|
||||
<p>{% trans "If the button above doesn't work, verify that you entered your username correctly and try pasting this link into your browser:" %}</p>
|
||||
|
||||
<p><a href="{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}" class="link-text">{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}</a></p>
|
||||
|
||||
<p>{% trans "Your username is:" %} <strong>{{ user.get_username }}</strong></p>
|
||||
|
||||
<p>{% trans "If you did not request this, please ignore this email." %}</p>
|
||||
|
||||
<p>{% trans "Thanks," %}<br>{% trans "The" %} {{ site_name }} {% trans "Team" %}</p>
|
||||
{% endblock %}
|
||||
1
core/templates/core/emails/password_reset_subject.txt
Normal file
1
core/templates/core/emails/password_reset_subject.txt
Normal file
@ -0,0 +1 @@
|
||||
{% trans "Password reset on" %} {{ site_name }}
|
||||
@ -4,36 +4,52 @@
|
||||
{% block title %}{% trans "Login" %} | masarX{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="py-5 bg-light" style="min-height: 80vh;">
|
||||
<div class="container py-5">
|
||||
<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-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="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>
|
||||
|
||||
<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>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ field.label }}</label>
|
||||
<label class="form-label fw-medium">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.errors %}
|
||||
<div class="text-danger small">{{ field.errors }}</div>
|
||||
<div class="text-danger small mt-1">{{ field.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn btn-masarx-primary w-100 py-2 mt-3">{% trans "Login" %}</button>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="text-center">
|
||||
<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>
|
||||
</form>
|
||||
<div class="text-center mt-4">
|
||||
<p class="mb-0 text-muted">{% trans "Don't have an account?" %} <a href="{% url 'register' %}" class="text-masarx-orange">{% trans "Register here" %}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -44,14 +60,30 @@
|
||||
<style>
|
||||
.form-control {
|
||||
border-radius: 8px;
|
||||
padding: 10px 15px;
|
||||
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;
|
||||
font-weight: 600;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
44
core/templates/core/password_reset_complete.html
Normal file
44
core/templates/core/password_reset_complete.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Password Reset Complete" %} | 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 text-center">
|
||||
<div class="mb-4 text-success">
|
||||
<i class="bi bi-check-circle-fill display-1"></i>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-3">{% trans "Password Changed!" %}</h3>
|
||||
<p class="text-muted mb-4">
|
||||
{% trans "Your password has been set. You may go ahead and log in now." %}
|
||||
</p>
|
||||
<a href="{% url 'login' %}" class="btn btn-masarx-primary w-100 py-2 fw-bold">
|
||||
{% trans "Log In" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
64
core/templates/core/password_reset_confirm.html
Normal file
64
core/templates/core/password_reset_confirm.html
Normal file
@ -0,0 +1,64 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "New Password" %} | 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">
|
||||
<div class="text-center mb-4">
|
||||
<h3 class="fw-bold">{% trans "Set New Password" %}</h3>
|
||||
<p class="text-muted">{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
</div>
|
||||
|
||||
<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.help_text %}
|
||||
<div class="form-text small">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<div class="text-danger small mt-1">{{ field.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn btn-masarx-primary w-100 py-2 mt-3 fw-bold">{% trans "Change Password" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
32
core/templates/core/password_reset_done.html
Normal file
32
core/templates/core/password_reset_done.html
Normal file
@ -0,0 +1,32 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Email Sent" %} | 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 text-center">
|
||||
<div class="mb-4 text-success">
|
||||
<i class="bi bi-envelope-check-fill display-1"></i>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-3">{% trans "Check Your Email" %}</h3>
|
||||
<p class="text-muted mb-4">
|
||||
{% trans "We've sent you instructions on how to reset your password. If an account exists with the email you entered, you will receive them shortly." %}
|
||||
</p>
|
||||
<p class="text-muted small mb-4">
|
||||
{% trans "If you don't receive an email, please check your spam folder." %}
|
||||
</p>
|
||||
<a href="{% url 'login' %}" class="btn btn-outline-secondary w-100 py-2 rounded-3">
|
||||
{% trans "Return to Login" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
72
core/templates/core/password_reset_form.html
Normal file
72
core/templates/core/password_reset_form.html
Normal file
@ -0,0 +1,72 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Reset Password" %} | 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">
|
||||
<div class="text-center mb-4">
|
||||
{% if platform_profile.logo %}
|
||||
<img src="{{ platform_profile.logo.url }}" alt="{{ platform_profile.name }}" height="60" 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 "Reset Password" %}</h4>
|
||||
<p class="text-muted mb-0">{% trans "Enter your email address and we'll send you a link to reset your password." %}</p>
|
||||
</div>
|
||||
|
||||
<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 %}
|
||||
<button type="submit" class="btn btn-masarx-primary w-100 py-2 fw-bold">{% trans "Send Reset Link" %}</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<a href="{% url 'login' %}" class="text-decoration-none text-muted">
|
||||
<i class="bi bi-arrow-left me-1"></i> {% trans "Back to Login" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.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; /* Darker orange */
|
||||
border-color: #e66a00;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -120,7 +120,17 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="{{ form.receiver_phone.id_for_label }}">{{ form.receiver_phone.label }}</label>
|
||||
{{ form.receiver_phone }}
|
||||
<div class="d-flex gap-2">
|
||||
<div class="flex-shrink-0" style="width: 140px;">
|
||||
{{ form.receiver_phone_code }}
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
{{ form.receiver_phone }}
|
||||
</div>
|
||||
</div>
|
||||
{% if form.receiver_phone_code.errors %}
|
||||
<div class="text-danger small">{{ form.receiver_phone_code.errors }}</div>
|
||||
{% endif %}
|
||||
{% if form.receiver_phone.errors %}
|
||||
<div class="text-danger small">{{ form.receiver_phone.errors }}</div>
|
||||
{% endif %}
|
||||
|
||||
BIN
core/templatetags/__pycache__/core_tags.cpython-311.pyc
Normal file
BIN
core/templatetags/__pycache__/core_tags.cpython-311.pyc
Normal file
Binary file not shown.
8
core/templatetags/core_tags.py
Normal file
8
core/templatetags/core_tags.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django import template
|
||||
from core.models import PlatformProfile
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def get_platform_profile():
|
||||
return PlatformProfile.objects.first()
|
||||
23
core/urls.py
23
core/urls.py
@ -9,6 +9,27 @@ urlpatterns = [
|
||||
path('register/', views.register, name='register'),
|
||||
path('register/verify/', views.verify_registration, name='verify_registration'),
|
||||
|
||||
# Password Reset URLs
|
||||
path('password-reset/', auth_views.PasswordResetView.as_view(
|
||||
template_name='core/password_reset_form.html',
|
||||
email_template_name='core/emails/password_reset_email.html',
|
||||
subject_template_name='core/emails/password_reset_subject.txt',
|
||||
success_url='/password-reset/done/'
|
||||
), name='password_reset'),
|
||||
|
||||
path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(
|
||||
template_name='core/password_reset_done.html'
|
||||
), name='password_reset_done'),
|
||||
|
||||
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(
|
||||
template_name='core/password_reset_confirm.html',
|
||||
success_url='/reset/done/'
|
||||
), name='password_reset_confirm'),
|
||||
|
||||
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(
|
||||
template_name='core/password_reset_complete.html'
|
||||
), name='password_reset_complete'),
|
||||
|
||||
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'),
|
||||
@ -27,4 +48,4 @@ urlpatterns = [
|
||||
path('profile/', views.profile_view, name='profile'),
|
||||
path('profile/edit/', views.edit_profile, name='edit_profile'),
|
||||
path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'),
|
||||
]
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user