user profile
This commit is contained in:
parent
59204ba309
commit
0bccc28caf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -84,6 +84,75 @@ class UserRegistrationForm(forms.ModelForm):
|
||||
profile.save()
|
||||
return user
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
first_name = forms.CharField(label=_("First Name"), max_length=150, widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
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_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'}))
|
||||
|
||||
otp_method = forms.ChoiceField(
|
||||
choices=[('email', _('Email')), ('whatsapp', _('WhatsApp'))],
|
||||
label=_('Verify changes via'),
|
||||
widget=forms.RadioSelect,
|
||||
initial='email'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['profile_picture', 'phone_number', 'address', 'country', 'governate', 'city']
|
||||
widgets = {
|
||||
'country': forms.Select(attrs={'class': 'form-control'}),
|
||||
'governate': forms.Select(attrs={'class': 'form-control'}),
|
||||
'city': forms.Select(attrs={'class': 'form-control'}),
|
||||
}
|
||||
labels = {
|
||||
'country': _('Country'),
|
||||
'governate': _('Governate'),
|
||||
'city': _('City'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance.user:
|
||||
self.fields['first_name'].initial = self.instance.user.first_name
|
||||
self.fields['last_name'].initial = self.instance.user.last_name
|
||||
self.fields['email'].initial = self.instance.user.email
|
||||
|
||||
lang = get_language()
|
||||
name_field = 'name_ar' if lang == 'ar' else 'name_en'
|
||||
|
||||
self.fields['country'].queryset = Country.objects.all().order_by(name_field)
|
||||
|
||||
# Default Country logic (Oman)
|
||||
oman = Country.objects.filter(name_en='Oman').first()
|
||||
|
||||
# Initial QS setup
|
||||
self.fields['governate'].queryset = Governate.objects.none()
|
||||
self.fields['city'].queryset = City.objects.none()
|
||||
|
||||
if 'country' in self.data:
|
||||
try:
|
||||
country_id = int(self.data.get('country'))
|
||||
self.fields['governate'].queryset = Governate.objects.filter(country_id=country_id).order_by(name_field)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
elif self.instance.pk and self.instance.country:
|
||||
self.fields['governate'].queryset = self.instance.country.governate_set.order_by(name_field)
|
||||
elif oman:
|
||||
self.fields['governate'].queryset = Governate.objects.filter(country=oman).order_by(name_field)
|
||||
|
||||
if 'governate' in self.data:
|
||||
try:
|
||||
gov_id = int(self.data.get('governate'))
|
||||
self.fields['city'].queryset = City.objects.filter(governate_id=gov_id).order_by(name_field)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
elif self.instance.pk and self.instance.governate:
|
||||
self.fields['city'].queryset = self.instance.governate.city_set.order_by(name_field)
|
||||
|
||||
class ParcelForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Parcel
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-25 11:40
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0008_platformprofile_privacy_policy_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='address',
|
||||
field=models.CharField(blank=True, max_length=255, verbose_name='Address'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='profile_picture',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='profile_pics/', verbose_name='Profile Picture'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OTPVerification',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.CharField(max_length=6)),
|
||||
('purpose', models.CharField(choices=[('profile_update', 'Profile Update'), ('password_reset', 'Password Reset')], default='profile_update', max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('is_verified', models.BooleanField(default=False)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import get_language
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
import uuid
|
||||
|
||||
class Country(models.Model):
|
||||
@ -67,6 +68,8 @@ class Profile(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name=_('User'))
|
||||
role = models.CharField(_('Role'), max_length=20, choices=ROLE_CHOICES, default='shipper')
|
||||
phone_number = models.CharField(_('Phone Number'), max_length=20, blank=True)
|
||||
profile_picture = models.ImageField(_('Profile Picture'), upload_to='profile_pics/', blank=True, null=True)
|
||||
address = models.CharField(_('Address'), max_length=255, blank=True)
|
||||
|
||||
country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Country'))
|
||||
governate = models.ForeignKey(Governate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Governate'))
|
||||
@ -163,3 +166,18 @@ class PlatformProfile(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _('Platform Profile')
|
||||
verbose_name_plural = _('Platform Profile')
|
||||
|
||||
class OTPVerification(models.Model):
|
||||
PURPOSE_CHOICES = (
|
||||
('profile_update', _('Profile Update')),
|
||||
('password_reset', _('Password Reset')),
|
||||
)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
code = models.CharField(max_length=6)
|
||||
purpose = models.CharField(max_length=20, choices=PURPOSE_CHOICES, default='profile_update')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
is_verified = models.BooleanField(default=False)
|
||||
|
||||
def is_valid(self):
|
||||
# OTP valid for 10 minutes
|
||||
return self.created_at >= timezone.now() - timezone.timedelta(minutes=10)
|
||||
|
||||
@ -87,7 +87,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<span class="nav-link text-white-50">{% trans "Hello" %}, {{ user.username }}</span>
|
||||
<a href="{% url 'profile' %}" class="nav-link text-white-50">{% trans "Hello" %}, {{ user.username }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<form action="{% url 'logout' %}" method="post" class="d-inline">
|
||||
|
||||
94
core/templates/core/edit_profile.html
Normal file
94
core/templates/core/edit_profile.html
Normal file
@ -0,0 +1,94 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Edit Profile" %} | 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-8">
|
||||
<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>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% 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="d-flex gap-2 mt-4">
|
||||
<a href="{% url 'profile' %}" class="btn btn-outline-secondary w-50 py-2">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-masarx-primary w-50 py-2">{% trans "Save & Verify" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const countrySelect = document.getElementById('id_country');
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
.btn-masarx-primary {
|
||||
background-color: var(--accent-orange);
|
||||
border-color: var(--accent-orange);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
71
core/templates/core/profile.html
Normal file
71
core/templates/core/profile.html
Normal file
@ -0,0 +1,71 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{% trans "My Profile" %} | 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-8">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">{% trans "My Profile" %}</h2>
|
||||
<a href="{% url 'edit_profile' %}" class="btn btn-masarx-primary">
|
||||
{% trans "Edit Profile" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-5">
|
||||
{% if profile.profile_picture %}
|
||||
<img src="{{ profile.profile_picture.url }}" alt="Profile" class="rounded-circle img-thumbnail" style="width: 150px; height: 150px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center mx-auto" style="width: 150px; height: 150px;">
|
||||
<span class="text-white fs-1">{{ profile.user.first_name|first|upper }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h3 class="mt-3">{{ profile.user.get_full_name }}</h3>
|
||||
<p class="text-muted">{{ profile.get_role_display }}</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<label class="text-muted small text-uppercase">{% trans "Email" %}</label>
|
||||
<p class="fw-semibold">{{ profile.user.email }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="text-muted small text-uppercase">{% trans "Phone" %}</label>
|
||||
<p class="fw-semibold">{{ profile.phone_number|default:"-" }}</p>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="text-muted small text-uppercase">{% trans "Location" %}</label>
|
||||
<p class="fw-semibold">
|
||||
{{ profile.city.name|default:"" }}
|
||||
{% if profile.city and profile.governate %}, {% endif %}
|
||||
{{ profile.governate.name|default:"" }}
|
||||
{% if profile.governate and profile.country %}, {% endif %}
|
||||
{{ profile.country.name|default:"" }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="text-muted small text-uppercase">{% trans "Address" %}</label>
|
||||
<p class="fw-semibold">{{ profile.address|default:"-" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</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;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
44
core/templates/core/verify_otp.html
Normal file
44
core/templates/core/verify_otp.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Verify Profile Update" %} | 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">
|
||||
<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>
|
||||
<p class="text-center text-muted mb-4">
|
||||
{% trans "We have sent a verification code to your selected contact method. Please enter it below to save your changes." %}
|
||||
</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>
|
||||
<button type="submit" class="btn btn-masarx-primary w-100 py-2">{% trans "Verify & Save" %}</button>
|
||||
</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 %}
|
||||
@ -21,4 +21,8 @@ urlpatterns = [
|
||||
path('privacy-policy/', views.privacy_policy, name='privacy_policy'),
|
||||
path('terms-conditions/', views.terms_conditions, name='terms_conditions'),
|
||||
path('contact/', views.contact_view, name='contact'),
|
||||
|
||||
path('profile/', views.profile_view, name='profile'),
|
||||
path('profile/edit/', views.edit_profile_view, name='edit_profile'),
|
||||
path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'),
|
||||
]
|
||||
118
core/views.py
118
core/views.py
@ -2,8 +2,8 @@ from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth import login, authenticate, logout
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import Parcel, Profile, Country, Governate, City
|
||||
from .forms import UserRegistrationForm, ParcelForm, ContactForm
|
||||
from .models import Parcel, Profile, Country, Governate, City, OTPVerification
|
||||
from .forms import UserRegistrationForm, ParcelForm, ContactForm, UserProfileForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import get_language
|
||||
from django.contrib import messages
|
||||
@ -11,11 +11,15 @@ from django.http import JsonResponse
|
||||
from django.urls import reverse
|
||||
from .payment_utils import ThawaniPay
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
import random
|
||||
import string
|
||||
from .whatsapp_utils import (
|
||||
notify_shipment_created,
|
||||
notify_payment_received,
|
||||
notify_driver_assigned,
|
||||
notify_status_change
|
||||
notify_status_change,
|
||||
send_whatsapp_message
|
||||
)
|
||||
from .mail import send_contact_message
|
||||
|
||||
@ -208,3 +212,111 @@ def contact_view(request):
|
||||
else:
|
||||
form = ContactForm()
|
||||
return render(request, 'core/contact.html', {'form': form})
|
||||
|
||||
@login_required
|
||||
def profile_view(request):
|
||||
return render(request, 'core/profile.html', {'profile': request.user.profile})
|
||||
|
||||
@login_required
|
||||
def edit_profile_view(request):
|
||||
if request.method == 'POST':
|
||||
form = UserProfileForm(request.POST, request.FILES, instance=request.user.profile)
|
||||
if form.is_valid():
|
||||
# 1. Handle Image immediately (easier than session storage)
|
||||
if 'profile_picture' in request.FILES:
|
||||
request.user.profile.profile_picture = request.FILES['profile_picture']
|
||||
request.user.profile.save()
|
||||
|
||||
# 2. Store other data in session for verification
|
||||
data = form.cleaned_data
|
||||
# Remove objects that can't be serialized or we've already handled
|
||||
safe_data = {
|
||||
'first_name': data['first_name'],
|
||||
'last_name': data['last_name'],
|
||||
'email': data['email'],
|
||||
'phone_number': data['phone_number'],
|
||||
'address': data['address'],
|
||||
'country_id': data['country'].id if data['country'] else None,
|
||||
'governate_id': data['governate'].id if data['governate'] else None,
|
||||
'city_id': data['city'].id if data['city'] else None,
|
||||
}
|
||||
request.session['pending_profile_update'] = safe_data
|
||||
|
||||
# 3. Generate OTP
|
||||
code = ''.join(random.choices(string.digits, k=6))
|
||||
OTPVerification.objects.create(user=request.user, code=code, purpose='profile_update')
|
||||
|
||||
# 4. Send OTP
|
||||
method = data.get('otp_method', 'email')
|
||||
if method == 'whatsapp':
|
||||
# Use current phone if available, else new phone
|
||||
phone = request.user.profile.phone_number or data['phone_number']
|
||||
send_whatsapp_message(phone, f"Your verification code is: {code}")
|
||||
messages.info(request, _("Verification code sent to WhatsApp."))
|
||||
else:
|
||||
# Default to email
|
||||
send_mail(
|
||||
_('Verification Code'),
|
||||
f'Your verification code is: {code}',
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
[request.user.email],
|
||||
fail_silently=False,
|
||||
)
|
||||
messages.info(request, _("Verification code sent to email."))
|
||||
|
||||
return redirect('verify_otp')
|
||||
else:
|
||||
form = UserProfileForm(instance=request.user.profile)
|
||||
|
||||
return render(request, 'core/edit_profile.html', {'form': form})
|
||||
|
||||
@login_required
|
||||
def verify_otp_view(request):
|
||||
if request.method == 'POST':
|
||||
code = request.POST.get('code')
|
||||
try:
|
||||
otp = OTPVerification.objects.filter(
|
||||
user=request.user,
|
||||
code=code,
|
||||
purpose='profile_update',
|
||||
is_verified=False
|
||||
).latest('created_at')
|
||||
|
||||
if otp.is_valid():
|
||||
# Apply changes
|
||||
data = request.session.get('pending_profile_update')
|
||||
if data:
|
||||
# Update User
|
||||
request.user.first_name = data['first_name']
|
||||
request.user.last_name = data['last_name']
|
||||
request.user.email = data['email']
|
||||
request.user.save()
|
||||
|
||||
# Update Profile
|
||||
profile = request.user.profile
|
||||
profile.phone_number = data['phone_number']
|
||||
profile.address = data['address']
|
||||
if data.get('country_id'):
|
||||
profile.country_id = data['country_id']
|
||||
if data.get('governate_id'):
|
||||
profile.governate_id = data['governate_id']
|
||||
if data.get('city_id'):
|
||||
profile.city_id = data['city_id']
|
||||
profile.save()
|
||||
|
||||
# Cleanup
|
||||
otp.is_verified = True
|
||||
otp.save()
|
||||
del request.session['pending_profile_update']
|
||||
|
||||
messages.success(request, _("Profile updated successfully!"))
|
||||
return redirect('profile')
|
||||
else:
|
||||
messages.error(request, _("Session expired. Please try again."))
|
||||
return redirect('edit_profile')
|
||||
else:
|
||||
messages.error(request, _("Invalid or expired code."))
|
||||
except OTPVerification.DoesNotExist:
|
||||
messages.error(request, _("Invalid code."))
|
||||
|
||||
return render(request, 'core/verify_otp.html')
|
||||
Binary file not shown.
@ -689,3 +689,71 @@ msgstr "تم إرسال رسالتك بنجاح!"
|
||||
#: core/views.py:206
|
||||
msgid "There was an error sending your message. Please try again later."
|
||||
msgstr "حدث خطأ أثناء إرسال رسالتك. يرجى المحاولة مرة أخرى لاحقاً."
|
||||
|
||||
#: core/forms.py:new
|
||||
msgid "Profile Picture"
|
||||
msgstr "الصورة الشخصية"
|
||||
|
||||
#: core/forms.py:new
|
||||
msgid "Verify changes via"
|
||||
msgstr "التحقق من التغييرات عبر"
|
||||
|
||||
#: core/templates/core/profile.html:new
|
||||
msgid "My Profile"
|
||||
msgstr "ملفي الشخصي"
|
||||
|
||||
#: core/templates/core/profile.html:new
|
||||
msgid "Edit Profile"
|
||||
msgstr "تعديل الملف الشخصي"
|
||||
|
||||
#: core/templates/core/edit_profile.html:new
|
||||
msgid "Save & Verify"
|
||||
msgstr "حفظ وتحقق"
|
||||
|
||||
#: core/templates/core/verify_otp.html:new
|
||||
msgid "Verification Required"
|
||||
msgstr "التحقق مطلوب"
|
||||
|
||||
#: core/templates/core/verify_otp.html:new
|
||||
msgid "We have sent a verification code to your selected contact method. Please enter it below to save your changes."
|
||||
msgstr "لقد أرسلنا رمز التحقق إلى وسيلة الاتصال المحددة. يرجى إدخاله أدناه لحفظ تغييراتك."
|
||||
|
||||
#: core/templates/core/verify_otp.html:new
|
||||
msgid "Verification Code"
|
||||
msgstr "رمز التحقق"
|
||||
|
||||
#: core/templates/core/verify_otp.html:new
|
||||
msgid "Verify & Save"
|
||||
msgstr "تحقق وحفظ"
|
||||
|
||||
#: core/views.py:new
|
||||
msgid "Profile updated successfully!"
|
||||
msgstr "تم تحديث الملف الشخصي بنجاح!"
|
||||
|
||||
#: core/views.py:new
|
||||
msgid "Invalid or expired code."
|
||||
msgstr "الرمز غير صالح أو منتهي الصلاحية."
|
||||
|
||||
#: core/views.py:new
|
||||
msgid "Verification code sent to WhatsApp."
|
||||
msgstr "تم إرسال رمز التحقق إلى واتساب."
|
||||
|
||||
#: core/views.py:new
|
||||
msgid "Verification code sent to email."
|
||||
msgstr "تم إرسال رمز التحقق إلى البريد الإلكتروني."
|
||||
|
||||
#: core/views.py:new
|
||||
msgid "Session expired. Please try again."
|
||||
msgstr "انتهت صلاحية الجلسة. يرجى المحاولة مرة أخرى."
|
||||
|
||||
#: core/views.py:new
|
||||
msgid "Invalid code."
|
||||
msgstr "رمز غير صالح."
|
||||
|
||||
#: core/models.py:new
|
||||
msgid "Profile Update"
|
||||
msgstr "تحديث الملف الشخصي"
|
||||
|
||||
#: core/models.py:new
|
||||
msgid "Password Reset"
|
||||
msgstr "إعادة تعيين كلمة المرور"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user