add UserRegistration

This commit is contained in:
ismail 2025-09-03 14:24:52 +03:00
parent 21c7fedbba
commit 50c1d82e4c
12 changed files with 553 additions and 286 deletions

View File

@ -72,6 +72,7 @@ admin.site.register(models.UserActivityLog)
admin.site.register(models.DealersMake) admin.site.register(models.DealersMake)
admin.site.register(models.ExtraInfo) admin.site.register(models.ExtraInfo)
admin.site.register(models.Ticket) admin.site.register(models.Ticket)
admin.site.register(models.UserRegistration)
@admin.register(models.Car) @admin.register(models.Car)

View File

@ -9,7 +9,7 @@ from plans.models import PlanPricing
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from inventory.validators import SaudiPhoneNumberValidator from inventory.validators import SaudiPhoneNumberValidator
from .models import CustomGroup, Status, Stage from .models import CustomGroup, Status, Stage, UserRegistration
from .mixins import AddClassMixin from .mixins import AddClassMixin
from django_ledger.forms.invoice import ( from django_ledger.forms.invoice import (
InvoiceModelCreateForm as InvoiceModelCreateFormBase, InvoiceModelCreateForm as InvoiceModelCreateFormBase,
@ -2255,3 +2255,10 @@ class TicketResolutionForm(forms.ModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Limit status choices to resolution options # Limit status choices to resolution options
self.fields["status"].choices = [("resolved", "Resolved"), ("closed", "Closed")] self.fields["status"].choices = [("resolved", "Resolved"), ("closed", "Closed")]
class CarDealershipRegistrationForm(forms.ModelForm):
class Meta:
model = UserRegistration
fields = "__all__"

View File

@ -3858,3 +3858,42 @@ class CarImage(models.Model):
self.id, self.id,
task_name=f"generate_car_image_{self.car.vin}", task_name=f"generate_car_image_{self.car.vin}",
) )
class UserRegistration(models.Model):
name = models.CharField(_("Name"), max_length=255)
arabic_name = models.CharField(_("Arabic Name"), max_length=255)
email = models.EmailField(_("email address"), unique=True)
phone_number = models.CharField(
max_length=255,
verbose_name=_("Phone Number"),
validators=[SaudiPhoneNumberValidator()],
)
crn = models.CharField(_("Commercial Registration Number"), max_length=10, unique=True)
vrn = models.CharField(_("Vehicle Registration Number"), max_length=15, unique=True)
address = models.TextField(_("Address"))
password = models.CharField(_("Password"), max_length=255)
is_created = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
REQUIRED_FIELDS = ["username", "arabic_name", "crn", "vrn", "address", "phone_number"]
def __str__(self):
return self.email
def create_account(self):
from django_q.tasks import async_task
from .tasks import create_user_dealer
password = User.objects.make_random_password()
if self.is_created:
return
dealer = create_user_dealer(self.email,password,self.name,self.arabic_name,self.phone,self.crn,self.vrn,self.address)
if dealer:
self.is_created = True
self.password = password
self.save()

View File

@ -72,12 +72,12 @@ User = get_user_model()
# check with marwan # check with marwan
@receiver(post_save, sender=models.Car) # @receiver(post_save, sender=models.Car)
def create_dealers_make(sender, instance, created, **kwargs): # def create_dealers_make(sender, instance, created, **kwargs):
if created: # if created:
models.DealersMake.objects.get_or_create( # models.DealersMake.objects.get_or_create(
dealer=instance.dealer, car_make=instance.id_car_make # dealer=instance.dealer, car_make=instance.id_car_make
) # )
@receiver(post_save, sender=models.Car) @receiver(post_save, sender=models.Car)
@ -325,6 +325,7 @@ def create_item_model(sender, instance, created, **kwargs):
inventory.save() inventory.save()
else: else:
instance.item_model.default_amount = instance.marked_price instance.item_model.default_amount = instance.marked_price
instance.item_model.save()
# inventory = entity.create_item_inventory( # inventory = entity.create_item_inventory(
# name=instance.vin, # name=instance.vin,
@ -1386,3 +1387,15 @@ def handle_car_image(sender, instance, created, **kwargs):
except Exception as e: except Exception as e:
logger.error(f"Error handling car image for {car.vin}: {e}") logger.error(f"Error handling car image for {car.vin}: {e}")
@receiver(post_save, sender=models.UserRegistration)
def handle_user_registration(sender, instance, created, **kwargs):
if instance.is_created:
send_email(
"Account Created",
f"Your account has been created and you can login with your email and password: {instance.password}",
settings.DEFAULT_FROM_EMAIL,
[instance.email],
fail_silently=False,
)

View File

@ -1,3 +1,4 @@
import time
import base64 import base64
import logging import logging
import requests import requests
@ -123,7 +124,7 @@ def retry_entity_creation(dealer_id, retry_count=0):
Retry entity creation if initial attempt failed Retry entity creation if initial attempt failed
""" """
from .models import Dealer from .models import Dealer
from yourapp.models import EntityModel from django_ledger.models import EntityModel
max_retries = 3 max_retries = 3

View File

@ -2,14 +2,16 @@ from inventory.utils import get_user_type
from . import views from . import views
from django.urls import path from django.urls import path
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import RedirectView from django.views.generic import RedirectView,TemplateView
from django_tables2.export.export import TableExport from django_tables2.export.export import TableExport
from django.conf.urls import handler403, handler400, handler404, handler500 from django.conf.urls import handler403, handler400, handler404, handler500
urlpatterns = [ urlpatterns = [
# main URLs # main URLs
path("", views.WelcomeView, name="welcome"), path("", views.WelcomeView, name="welcome"),
path("signup/", views.dealer_signup, name="account_signup"), # path("signup/", views.dealer_signup, name="account_signup"),
path('signup/', views.CarDealershipSignUpView.as_view(), name='account_signup'),
path('success/', TemplateView.as_view(template_name='account/success.html'), name='registration_success'),
path("", views.HomeView, name="home"), path("", views.HomeView, name="home"),
path('refund-policy/',views.refund_policy,name='refund_policy'), path('refund-policy/',views.refund_policy,name='refund_policy'),
path("<slug:dealer_slug>/", views.HomeView, name="home"), path("<slug:dealer_slug>/", views.HomeView, name="home"),

View File

@ -6,7 +6,7 @@ import re
class SaudiPhoneNumberValidator(RegexValidator): class SaudiPhoneNumberValidator(RegexValidator):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__( super().__init__(
regex=r"^(\+9665|05)[0-9]{8}$", regex=r"^(\+9665|05|9665)[0-9]{8}$",
message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)"), message=_("Enter a valid Saudi phone number (05XXXXXXXX or +9665XXXXXXXX)"),
) )

View File

@ -5125,7 +5125,8 @@ def create_sale_order(request, dealer_slug, pk):
f"KeyError: 'car_info' or 'status' key missing when attempting to update status to 'sold' for item.item_model PK: {getattr(item.item_model, 'pk', 'N/A')}." f"KeyError: 'car_info' or 'status' key missing when attempting to update status to 'sold' for item.item_model PK: {getattr(item.item_model, 'pk', 'N/A')}."
) )
pass pass
item.item_model.car.sold_date=timezone.now() # to be checked added by faheed
item.item_model.car.save()# to be checked added byfaheed
item.item_model.car.mark_as_sold() item.item_model.car.mark_as_sold()
messages.success(request, "Sale Order created successfully") messages.success(request, "Sale Order created successfully")
@ -7539,17 +7540,17 @@ class NotificationListView(LoginRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
return models.Notification.objects.filter(user=self.request.user) return models.Notification.objects.filter(user=self.request.user)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
user_notifications = self.get_queryset() user_notifications = self.get_queryset()
# Calculate the number of total, read and unread notifications # Calculate the number of total, read and unread notifications
context['total_count'] = user_notifications.count() context['total_count'] = user_notifications.count()
context['read_count'] = user_notifications.filter(is_read=True).count() context['read_count'] = user_notifications.filter(is_read=True).count()
context['unread_count'] = user_notifications.filter(is_read=False).count() context['unread_count'] = user_notifications.filter(is_read=False).count()
return context return context
@ -11078,7 +11079,7 @@ def purchase_report_view(request, dealer_slug):
bills = po.get_po_bill_queryset() bills = po.get_po_bill_queryset()
vendors = set([bill.vendor.vendor_name for bill in bills]) vendors = set([bill.vendor.vendor_name for bill in bills])
vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A" vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A"
data.append({ data.append({
"po_number": po.po_number, "po_number": po.po_number,
"po_created": po.created, "po_created": po.created,
@ -11194,7 +11195,7 @@ def car_sale_report_view(request, dealer_slug):
cars_sold = cars_sold.filter(year=selected_year) cars_sold = cars_sold.filter(year=selected_year)
if selected_stock_type: if selected_stock_type:
cars_sold = cars_sold.filter(stock_type=selected_stock_type) cars_sold = cars_sold.filter(stock_type=selected_stock_type)
# Corrected: Apply date filters using the 'sold_date' field # Corrected: Apply date filters using the 'sold_date' field
if start_date_str: if start_date_str:
try: try:
@ -11202,7 +11203,7 @@ def car_sale_report_view(request, dealer_slug):
cars_sold = cars_sold.filter(sold_date__gte=start_date) cars_sold = cars_sold.filter(sold_date__gte=start_date)
except (ValueError, TypeError): except (ValueError, TypeError):
pass pass
if end_date_str: if end_date_str:
try: try:
end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date() end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date()
@ -11224,7 +11225,7 @@ def car_sale_report_view(request, dealer_slug):
total_vat_from_additonals = sum([car.get_additional_services()['services_vat'] for car in cars_sold]) total_vat_from_additonals = sum([car.get_additional_services()['services_vat'] for car in cars_sold])
total_vat_collected = total_vat_on_cars + total_vat_from_additonals total_vat_collected = total_vat_on_cars + total_vat_from_additonals
total_revenue_collected = total_revenue_from_cars + total_revenue_from_additonals total_revenue_collected = total_revenue_from_cars + total_revenue_from_additonals
total_discount = cars_sold.aggregate(total=Sum('discount_amount'))['total'] or 0 total_discount = cars_sold.aggregate(total=Sum('discount_amount'))['total'] or 0
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
@ -11263,7 +11264,7 @@ def car_sale_report_view(request, dealer_slug):
@login_required @login_required
def get_filtered_choices(request, dealer_slug): def get_filtered_choices(request, dealer_slug):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
# Get all filter parameters from the request # Get all filter parameters from the request
selected_make = request.GET.get('make') selected_make = request.GET.get('make')
selected_model = request.GET.get('model') selected_model = request.GET.get('model')
@ -11277,10 +11278,10 @@ def get_filtered_choices(request, dealer_slug):
# Apply filters based on what is selected # Apply filters based on what is selected
if selected_make: if selected_make:
queryset = queryset.filter(id_car_make__name=selected_make) queryset = queryset.filter(id_car_make__name=selected_make)
if selected_model: if selected_model:
queryset = queryset.filter(id_car_model__name=selected_model) queryset = queryset.filter(id_car_model__name=selected_model)
if selected_serie: if selected_serie:
queryset = queryset.filter(id_car_serie__name=selected_serie) queryset = queryset.filter(id_car_serie__name=selected_serie)
@ -11351,7 +11352,7 @@ def car_sale_report_csv_export(request, dealer_slug):
cars_sold = cars_sold.filter(year=selected_year) cars_sold = cars_sold.filter(year=selected_year)
if selected_stock_type: if selected_stock_type:
cars_sold = cars_sold.filter(stock_type=selected_stock_type) cars_sold = cars_sold.filter(stock_type=selected_stock_type)
# Corrected: Apply date filters for CSV export # Corrected: Apply date filters for CSV export
if start_date_str: if start_date_str:
try: try:
@ -11669,6 +11670,18 @@ class CharOfAccountModelActionView(CharOfAccountModelActionViewBase):
#for refund policy class CarDealershipSignUpView(CreateView):
def refund_policy(request): model = models.UserRegistration
return render(request,'haikal_policy/refund_policy.html') form_class = forms.CarDealershipRegistrationForm
template_name = 'account/signup-wizard.html'
success_url = reverse_lazy('registration_success')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = _('Car Dealership Registration')
return context
def form_valid(self, form):
response = super().form_valid(form)
messages.success(self.request, _('Your request has been submitted. We will contact you soon.'))
return response

View File

@ -58,6 +58,7 @@ django-widget-tweaks==1.5.0
djangorestframework==3.16.0 djangorestframework==3.16.0
djhtml==3.0.8 djhtml==3.0.8
djlint==1.36.4 djlint==1.36.4
dnspython==2.7.0
docopt==0.6.2 docopt==0.6.2
EditorConfig==0.17.1 EditorConfig==0.17.1
Faker==37.4.0 Faker==37.4.0
@ -77,6 +78,8 @@ hyperlink==21.0.0
icalendar==6.3.1 icalendar==6.3.1
idna==3.10 idna==3.10
incremental==24.7.2 incremental==24.7.2
iron-core==1.2.1
iron-mq==0.9
jiter==0.10.0 jiter==0.10.0
jsbeautifier==1.15.4 jsbeautifier==1.15.4
json5==0.12.0 json5==0.12.0
@ -114,6 +117,7 @@ pycparser==2.22
pydantic==2.11.7 pydantic==2.11.7
pydantic_core==2.33.2 pydantic_core==2.33.2
Pygments==2.19.2 Pygments==2.19.2
pymongo==4.14.1
pyOpenSSL==25.1.0 pyOpenSSL==25.1.0
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
python-dotenv==1.1.1 python-dotenv==1.1.1

View File

@ -1,6 +1,7 @@
{% extends "welcome_base.html" %} {% extends "welcome_base.html" %}
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% load i18n static %} {% load i18n static %}
{% block content %} {% block content %}
<section class="main my-2"> <section class="main my-2">
<div class="container" style="max-width:60rem;"> <div class="container" style="max-width:60rem;">
@ -20,272 +21,27 @@
</div> </div>
</a> </a>
<div class="text-center"> <div class="text-center">
<h3 class="text-body-highlight">{% trans 'Sign Up' %}</h3> <h3 class="text-body-highlight">{% trans 'Car Dealership Registration' %}</h3>
<p class="text-body-tertiary fs-9">{% trans 'Create your account today' %}</p> <p class="text-body-tertiary fs-9">{% trans 'Create your dealership account today' %}</p>
</div> </div>
<div data-signals="{ form1:{email:'',password:'',confirm_password:''}, form2:{name:'',arabic_name:'',phone_number:''}, form3:{crn:'',vrn:'',address:''}, form1_valid:true, form2_valid:true, form3_valid:true, email_valid:true, password_valid:true, phone_number_valid:true }"
class="card theme-wizard" <form method="post" action="{% url 'account_signup' %}" class="needs-validation">
data-theme-wizard="data-theme-wizard"> {% csrf_token %}
<div class="card-header pt-3 pb-2 "> <div class="card theme-wizard">
<ul class="nav justify-content-between nav-wizard nav-wizard-success" <div class="card-body pt-4 pb-0">
role="tablist"> {{ form|crispy }}
<li class="nav-item" role="presentation">
<a class="nav-link active fw-semibold"
href="#bootstrap-wizard-validation-tab1"
data-bs-toggle="tab"
data-wizard-step="1"
aria-selected="true"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-lock"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span>
</div>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link fw-semibold"
href="#bootstrap-wizard-validation-tab2"
data-bs-toggle="tab"
data-wizard-step="2"
aria-selected="false"
tabindex="-1"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-user"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span>
</div>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link fw-semibold"
href="#bootstrap-wizard-validation-tab3"
data-bs-toggle="tab"
data-wizard-step="3"
aria-selected="false"
tabindex="-1"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle">
<svg class="fa fa-file-lines">
</svg>
</span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Extra' %}</span>
</div>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link fw-semibold"
href="#bootstrap-wizard-validation-tab4"
data-bs-toggle="tab"
data-wizard-step="4"
aria-selected="false"
tabindex="-1"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-check"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span>
</div>
</a>
</li>
</ul>
</div>
<div class="card-body pt-4 pb-0">
<div class="tab-content" data-signals-current_form="1">
<div class="tab-pane active"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab1"
id="bootstrap-wizard-validation-tab1">
<form class="needs-validation"
id="wizardValidationForm1"
novalidate="novalidate"
data-wizard-form="1"
data-ref-f1>
<div class="mb-3">
<label for="email"
data-class="{'text-danger':!$email_valid}"
class="form-label">
{% trans "Email" %}
<span data-show="!$email_valid" class="text-danger">*</span>
</label>
<input data-on-input="$email_valid = validateEmail($form1.email)"
data-on-blur="$email_valid = validateEmail($form1.email)"
data-bind-form1.email
data-class="{'is-invalid': !$email_valid , 'is-valid': ($email_valid && $form1.email)}"
type="email"
class="form-control"
id="email"
name="email"
required>
<div class="invalid-feedback" data-show="!$email_valid">{% trans "Please enter a valid email address" %}</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">{% trans "Password" %}</label>
<input data-bind-form1.password
type="password"
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
class="form-control"
data-class="{'is-invalid':($form1.password.length && $form1.password.length < 8),'is-valid':$form1.password.length > 8 }"
id="password"
name="password"
required>
<div class="invalid-feedback" data-show="!$password_valid">
{% trans "Password does not match. or length is less than 8 characters." %}
</div>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">{% trans "Confirm Password" %}</label>
<span class="text-danger" data-show="!$password_valid">*</span>
<input data-bind-form1.confirm_password
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
type="password"
class="form-control"
data-class="{'is-invalid':!$password_valid,'is-valid':($password_valid&& $form1.confirm_password)}"
id="confirm_password"
name="confirm_password"
required>
<div class="invalid-feedback" data-show="!$password_valid">
{% trans "Password does not match. or length is less than 8 characters." %}
</div>
</div>
</form>
</div>
<div class="tab-pane"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab2"
id="bootstrap-wizard-validation-tab2">
<form class="needs-validation"
id="wizardValidationForm2"
novalidate="novalidate"
data-wizard-form="2"
data-ref-f2>
<div class="mb-3">
<label for="name" class="form-label">{% trans "Name" %}</label>
<input data-bind-form2.name
type="text"
class="form-control"
id="name"
name="name"
required>
</div>
<div class="mb-3">
<label for="arabic_name" class="form-label">{% trans "Arabic Name" %}</label>
<input data-bind-form2.arabic_name
type="text"
class="form-control"
id="arabic_name"
name="arabic_name"
required>
</div>
<div class="mb-3">
<label for="phone_number" class="form-label">{% trans "Phone Number" %}</label>
<span data-show="!$phone_number_valid" class="text-danger">*</span>
<input data-bind-form2.phone_number
type="tel"
data-class="{'is-invalid':!$phone_number_valid}"
class="form-control"
id="phone_number"
name="phone_number"
required
data-on-input="$phone_number_valid = validate_sa_phone_number($form2.phone_number)">
<div class="invalid-feedback" data-show="!$phone_number_valid">{% trans "Please enter a valid phone number" %}</div>
</div>
</form>
</div>
<div class="tab-pane"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab3"
id="bootstrap-wizard-validation-tab3">
<form class="needs-validation"
id="wizardValidationForm3"
novalidate="novalidate"
data-wizard-form="3"
data-ref-f3>
<div class="mb-3">
<label for="crn" class="form-label">{% trans "CRN" %}</label>
<input data-bind-form3.crn
type="text"
class="form-control"
id="crn"
name="crn"
required>
</div>
<div class="mb-3">
<label for="vrn" class="form-label">{% trans "VRN" %}</label>
<input data-bind-form3.vrn
type="text"
class="form-control"
id="vrn"
name="vrn"
required>
</div>
<div class="mb-3">
<label for="address" class="form-label">{% trans "Address" %}</label>
<textarea data-bind-form3.address
class="form-control"
id="address"
name="address"
required></textarea>
</div>
</form>
</div>
<div class="tab-pane"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab4"
id="bootstrap-wizard-validation-tab4">
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start">
<img class="d-dark-none"
src="{% static 'images/spot-illustrations/38.webp' %}"
alt=""
width="220">
<img class="d-light-none"
src="{% static 'images/spot-illustrations/dark_38.webp' %}"
alt=""
width="220">
</div>
</div>
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start">
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
<p class="text-body-emphasis fs-9">
{% trans 'Now you can access your account' %}
<br>
{% trans 'anytime' %} {% trans 'anywhere' %}
</p>
<button data-on-click="sendFormData()"
class="btn btn-primary px-6"
id='submit_btn'>{% trans 'Submit' %}</button>
</div>
</div>
</div>
</div>
</div> </div>
</div> <button class="btn btn-primary" type="submit">{% trans 'Sign Up' %}</button>
<div data-computed-form1_valid="validatePassword($form1.password,$form1.confirm_password) && validateEmail($form1.email)" </form>
class="card-footer border-top-0"
data-wizard-footer="data-wizard-footer">
<div class="d-flex pager wizard list-inline mb-0">
<button class="d-none btn btn-link ps-0"
type="button"
data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
<div class="flex-1 text-end">
<button data-attr-disabled="!$form1_valid"
data-attr-disabled="!$phone_number_valid"
class="btn btn-phoenix-primary px-6 px-sm-6 next"
type="button"
id="next_btn"
data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="mx-auto mt-4 text-center"> <div class="mx-auto mt-4 text-center">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#contentModal" data-url="{% url 'refund_policy' %}"> <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#contentModal" data-url="{% url 'refund_policy' %}">
Our Refund Policy Our Refund Policy
</button> </button>
</div> </div>
</div> </div>
</section> </section>
<div class="modal fade" id="contentModal" tabindex="-1" aria-labelledby="contentModalLabel" aria-hidden="true"> <div class="modal fade" id="contentModal" tabindex="-1" aria-labelledby="contentModalLabel" aria-hidden="true">
@ -301,7 +57,7 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'footer.html' %} {% include 'footer.html' %}
<script src="{% static 'js/phoenix.js' %}"></script> <script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %} {% endblock content %}
@ -416,7 +172,7 @@ document.addEventListener('DOMContentLoaded', function() {
const button = event.relatedTarget; const button = event.relatedTarget;
// Extract the URL from the button's data-url attribute // Extract the URL from the button's data-url attribute
const url = button.getAttribute('data-url'); const url = button.getAttribute('data-url');
// Select the modal body element // Select the modal body element
const modalBody = contentModal.querySelector('.modal-body'); const modalBody = contentModal.querySelector('.modal-body');

View File

@ -0,0 +1,35 @@
{% extends "welcome_base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}
{% block content %}
<section class="main my-2">
<div class="container" style="max-width:60rem;">
<div class="row form-container" id="form-container">
<div class="col-12 ">
<a class="d-flex flex-center text-decoration-none mb-4"
href="{% url 'home' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
<img class="d-dark-none"
src="{% static 'images/logos/logo-d.png' %}"
alt="{% trans 'home' %}"
width="58" />
<img class="d-light-none"
src="{% static 'images/logos/logo.png' %}"
alt="{% trans 'home' %}"
width="58" />
</div>
</a>
<div class="text-center">
<h3 class="text-body-highlight">{% trans 'Account Created Successfully' %}</h3>
<p class="text-body-tertiary fs-9">
{% blocktrans %}
Thank you for registering at Haikal. We will contact you soon.
{% endblocktrans %}
</p>
</div>
</div>
</div>
</div>
</section>
{% endblock content %}

View File

@ -0,0 +1,396 @@
{% extends "welcome_base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}
{% block content %}
<section class="main my-2">
<div class="container" style="max-width:60rem;">
<div class="row form-container" id="form-container">
<div class="col-12 ">
<a class="d-flex flex-center text-decoration-none mb-4"
href="{% url 'home' %}">
<div class="d-flex align-items-center fw-bolder fs-3 d-inline-block">
<img class="d-dark-none"
src="{% static 'images/logos/logo-d.png' %}"
alt="{% trans 'home' %}"
width="58" />
<img class="d-light-none"
src="{% static 'images/logos/logo.png' %}"
alt="{% trans 'home' %}"
width="58" />
</div>
</a>
<div class="text-center">
<h3 class="text-body-highlight">{% trans 'Car Dealership Registration' %}</h3>
<p class="text-body-tertiary fs-9">{% trans 'Create your dealership account today' %}</p>
</div>
<div data-signals="{ form1:{email:'',password:'',confirm_password:''}, form2:{name:'',arabic_name:'',phone_number:''}, form3:{crn:'',vrn:'',address:''}, form1_valid:true, form2_valid:true, form3_valid:true, email_valid:true, password_valid:true, phone_number_valid:true }"
class="card theme-wizard"
data-theme-wizard="data-theme-wizard">
<div class="card-header pt-3 pb-2 ">
<ul class="nav justify-content-between nav-wizard nav-wizard-success"
role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active fw-semibold"
href="#bootstrap-wizard-validation-tab1"
data-bs-toggle="tab"
data-wizard-step="1"
aria-selected="true"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-lock"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Access' %}</span>
</div>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link fw-semibold"
href="#bootstrap-wizard-validation-tab2"
data-bs-toggle="tab"
data-wizard-step="2"
aria-selected="false"
tabindex="-1"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-user"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Account' %}</span>
</div>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link fw-semibold"
href="#bootstrap-wizard-validation-tab3"
data-bs-toggle="tab"
data-wizard-step="3"
aria-selected="false"
tabindex="-1"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle">
<svg class="fa fa-building">
</svg>
</span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Dealership' %}</span>
</div>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link fw-semibold"
href="#bootstrap-wizard-validation-tab4"
data-bs-toggle="tab"
data-wizard-step="4"
aria-selected="false"
tabindex="-1"
role="tab">
<div class="text-center d-inline-block">
<span class="nav-item-circle-parent"><span class="nav-item-circle"><span class="fa fa-check"></span></span></span><span class="d-none d-md-block mt-1 fs-9">{% trans 'Done' %}</span>
</div>
</a>
</li>
</ul>
</div>
<div class="card-body pt-4 pb-0">
<div class="tab-content" data-signals-current_form="1">
<div class="tab-pane active"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab1"
id="bootstrap-wizard-validation-tab1">
<form class="needs-validation"
id="wizardValidationForm1"
novalidate="novalidate"
data-wizard-form="1"
data-ref-f1>
<div class="mb-3">
<label for="email"
data-class="{'text-danger':!$email_valid}"
class="form-label">
{% trans "Email" %}
<span data-show="!$email_valid" class="text-danger">*</span>
</label>
<input data-on-input="$email_valid = validateEmail($form1.email)"
data-on-blur="$email_valid = validateEmail($form1.email)"
data-bind-form1.email
data-class="{'is-invalid': !$email_valid , 'is-valid': ($email_valid && $form1.email)}"
type="email"
class="form-control"
id="email"
name="email"
required>
<div class="invalid-feedback" data-show="!$email_valid">{% trans "Please enter a valid email address" %}</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">{% trans "Password" %}</label>
<input data-bind-form1.password
type="password"
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
class="form-control"
data-class="{'is-invalid':($form1.password.length && $form1.password.length < 8),'is-valid':$form1.password.length > 8 }"
id="password"
name="password1"
required>
<div class="invalid-feedback" data-show="!$password_valid">
{% trans "Password does not match. or length is less than 8 characters." %}
</div>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">{% trans "Confirm Password" %}</label>
<span class="text-danger" data-show="!$password_valid">*</span>
<input data-bind-form1.confirm_password
data-on-input="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
data-on-blur="$password_valid = validatePassword($form1.password,$form1.confirm_password)"
type="password"
class="form-control"
data-class="{'is-invalid':!$password_valid,'is-valid':($password_valid&& $form1.confirm_password)}"
id="confirm_password"
name="password2"
required>
<div class="invalid-feedback" data-show="!$password_valid">
{% trans "Password does not match. or length is less than 8 characters." %}
</div>
</div>
</form>
</div>
<div class="tab-pane"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab2"
id="bootstrap-wizard-validation-tab2">
<form class="needs-validation"
id="wizardValidationForm2"
novalidate="novalidate"
data-wizard-form="2"
data-ref-f2>
<div class="mb-3">
<label for="name" class="form-label">{% trans "Dealership Name" %}</label>
<input data-bind-form2.name
type="text"
class="form-control"
id="name"
name="username"
required>
</div>
<div class="mb-3">
<label for="arabic_name" class="form-label">{% trans "Arabic Name" %}</label>
<input data-bind-form2.arabic_name
type="text"
class="form-control"
id="arabic_name"
name="arabic_name"
required>
</div>
<div class="mb-3">
<label for="phone_number" class="form-label">{% trans "Phone Number" %}</label>
<span data-show="!$phone_number_valid" class="text-danger">*</span>
<input data-bind-form2.phone_number
type="tel"
data-class="{'is-invalid':!$phone_number_valid}"
class="form-control"
id="phone_number"
name="phone_number"
required
data-on-input="$phone_number_valid = validate_sa_phone_number($form2.phone_number)">
<div class="invalid-feedback" data-show="!$phone_number_valid">{% trans "Please enter a valid Saudi phone number (e.g., 05XXXXXXXX)" %}</div>
</div>
</form>
</div>
<div class="tab-pane"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab3"
id="bootstrap-wizard-validation-tab3">
<form class="needs-validation"
id="wizardValidationForm3"
novalidate="novalidate"
data-wizard-form="3"
data-ref-f3>
<div class="mb-3">
<label for="crn" class="form-label">{% trans "Commercial Registration Number (CRN)" %}</label>
<input data-bind-form3.crn
type="text"
class="form-control"
id="crn"
name="crn"
required>
</div>
<div class="mb-3">
<label for="vrn" class="form-label">{% trans "Vehicle Registration Number (VRN)" %}</label>
<input data-bind-form3.vrn
type="text"
class="form-control"
id="vrn"
name="vrn"
required>
</div>
<div class="mb-3">
<label for="address" class="form-label">{% trans "Business Address" %}</label>
<textarea data-bind-form3.address
class="form-control"
id="address"
name="address"
rows="4"
required></textarea>
</div>
</form>
</div>
<div class="tab-pane"
role="tabpanel"
aria-labelledby="bootstrap-wizard-validation-tab4"
id="bootstrap-wizard-validation-tab4">
<div class="row flex-center pb-8 pt-4 gx-3 gy-4">
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start">
<img class="d-dark-none"
src="{% static 'images/spot-illustrations/38.webp' %}"
alt=""
width="220">
<img class="d-light-none"
src="{% static 'images/spot-illustrations/dark_38.webp' %}"
alt=""
width="220">
</div>
</div>
<div class="col-12 col-sm-auto">
<div class="text-center text-sm-start">
<h5 class="mb-3">{% trans 'You are all set!' %}</h5>
<p class="text-body-emphasis fs-9">
{% trans 'Now you can access your dealership account' %}
<br>
{% trans 'anytime' %} {% trans 'anywhere' %}
</p>
<button data-on-click="sendFormData()"
class="btn btn-primary px-6"
id='submit_btn'>{% trans 'Complete Registration' %}</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div data-computed-form1_valid="validatePassword($form1.password,$form1.confirm_password) && validateEmail($form1.email)"
class="card-footer border-top-0"
data-wizard-footer="data-wizard-footer">
<div class="d-flex pager wizard list-inline mb-0">
<button class="d-none btn btn-link ps-0"
type="button"
data-wizard-prev-btn="data-wizard-prev-btn">{% trans 'Previous' %}</button>
<div class="flex-1 text-end">
<button data-attr-disabled="!$form1_valid"
data-attr-disabled="!$phone_number_valid"
class="btn btn-phoenix-primary px-6 px-sm-6 next"
type="button"
id="next_btn"
data-wizard-next-btn="data-wizard-next-btn">{% trans 'Next' %}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{% include 'footer.html' %}
<script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %}
{% block customJS %}
<script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<script type="module"
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.11/bundles/datastar.js"></script>
<script>
function validatePassword(password, confirmPassword) {
return password === confirmPassword && password.length > 7 && password !== '';
}
function validateEmail(email) {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(email) && email !== '';
}
function validateform2(name,arabic_name,phone_number) {
if (name === '' || arabic_name === '' || phone_number === '' || phone_number.length < 10 || !phone_number.startsWith('05')) {
return false;
}
return true
}
function validate_sa_phone_number(phone_number) {
const phone_numberRegex = /^05[0-9]{8}$/;
return phone_numberRegex.test(phone_number) && phone_numberRegex !== '';
}
function getAllFormData() {
const forms = document.querySelectorAll('.needs-validation');
const formData = {};
forms.forEach(form => {
const fields = form.querySelectorAll('input,textarea,select');
fields.forEach(field => {
formData[field.name] = field.value;
});
});
return formData;
}
function showLoading() {
Swal.fire({
title: "{% trans 'Please Wait' %}",
text: "{% trans 'Processing your registration' %}...",
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
}
function hideLoading() {
Swal.close();
}
function notify(tag,msg){
Swal.fire({
icon: tag,
titleText: msg
});
}
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
async function sendFormData() {
const formData = getAllFormData();
const url = "{% url 'car_dealership_signup' %}";
const csrftoken = getCookie('csrftoken');
try {
showLoading();
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(formData),
});
hideLoading();
if (response.ok) {
notify("success","{% trans 'Dealership account created successfully' %}");
setTimeout(() => {
window.location.href = "{% url 'home' %}";
}, 1500);
} else {
const data = await response.json();
if (data.errors) {
let errorMsg = '';
for (const [key, value] of Object.entries(data.errors)) {
errorMsg += `${value.join(', ')}\n`;
}
notify("error", errorMsg);
} else {
notify("error", "{% trans 'An error occurred during registration' %}");
}
}
} catch (error) {
notify("error", "{% trans 'Network error. Please try again.' %}");
}
}
</script>
{% endblock customJS %}