Merge branch 'main' into frontend

This commit is contained in:
Faheed 2025-09-07 12:44:01 +03:00
commit 094deebf58
53 changed files with 2221 additions and 1794 deletions

4
.gitignore vendored
View File

@ -24,6 +24,10 @@ dev_venv
*.bak *.bak
play.sh play.sh
git-sync.sh git-sync.sh
deploy*
git-sync.sh
update.sh
rollback.sh
# If you are using PyCharm # # If you are using PyCharm #
# User-specific stuff # User-specific stuff
.idea/**/workspace.xml .idea/**/workspace.xml

View File

@ -2,7 +2,7 @@
from django.contrib import admin from django.contrib import admin
from . import models from . import models
from django_ledger import models as ledger_models from django_ledger import models as ledger_models
from django.contrib import messages
# from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait # from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait
# from appointment import models as appointment_models # from appointment import models as appointment_models
from import_export.admin import ExportMixin from import_export.admin import ExportMixin
@ -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)
@ -174,3 +175,93 @@ class CarOptionAdmin(admin.ModelAdmin):
# class ItemTransactionModelAdmin(admin.ModelAdmin): # class ItemTransactionModelAdmin(admin.ModelAdmin):
# actions = [export_to_pdf_landscape, export_to_pdf_portrait] # actions = [export_to_pdf_landscape, export_to_pdf_portrait]
@admin.register(models.UserRegistration)
class UserRegistrationAdmin(admin.ModelAdmin):
# Fields to display in the list view
list_display = [
'name',
'arabic_name',
'email',
'crn',
'vrn',
'phone_number',
'is_created',
'created_at',
]
# Filters in the right sidebar
list_filter = [
'is_created',
'created_at',
]
# Searchable fields
search_fields = [
'name', 'arabic_name', 'email', 'crn', 'vrn', 'phone_number'
]
# Read-only fields in detail view
readonly_fields = [
'created_at', 'updated_at', 'is_created', 'password'
]
# Organize form layout
fieldsets = [
('Account Information', {
'fields': ('name', 'arabic_name', 'email', 'phone_number')
}),
('Business Details', {
'fields': ('crn', 'vrn', 'address')
}),
('Status', {
'fields': ('is_created', 'password', 'created_at', 'updated_at'),
'classes': ('collapse',)
}),
]
# Custom action to create accounts
actions = ['create_dealer_accounts']
@admin.action(description='Create dealer account(s) for selected registrations')
def create_dealer_accounts(self, request, queryset):
created_count = 0
already_created_count = 0
failed_count = 0
for registration in queryset:
try:
if not registration.is_created:
registration.create_account() # Your existing method
created_count += 1
else:
already_created_count += 1
except Exception as e:
self.message_user(
request,
f"Error creating account for {registration.name}: {str(e)}",
level=messages.ERROR
)
failed_count += 1
# Show summary message
if created_count > 0:
self.message_user(
request,
f"Successfully created {created_count} account(s).",
level=messages.SUCCESS
)
if already_created_count > 0:
self.message_user(
request,
f"{already_created_count} registration(s) were already created.",
level=messages.INFO
)
if failed_count > 0:
self.message_user(
request,
f"Failed to create {failed_count} account(s). Check logs.",
level=messages.ERROR
)

View File

@ -57,6 +57,7 @@ from .models import (
Tasks, Tasks,
Recall, Recall,
Ticket, Ticket,
UserRegistration
) )
from django_ledger import models as ledger_models from django_ledger import models as ledger_models
from django.forms import ( from django.forms import (
@ -363,6 +364,7 @@ class CarForm(
"receiving_date", "receiving_date",
"vendor", "vendor",
] ]
required_fields = ["vin","id_car_make", "id_car_model", "id_car_serie", "id_car_trim", "vendor"]
widgets = { widgets = {
"id_car_make": forms.Select(attrs={"class": "form-select form-select-sm"}), "id_car_make": forms.Select(attrs={"class": "form-select form-select-sm"}),
"receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}), "receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
@ -1259,19 +1261,8 @@ class OpportunityForm(forms.ModelForm):
label=_("Expected Closing Date"), widget=forms.DateInput(attrs={"type": "date"}) label=_("Expected Closing Date"), widget=forms.DateInput(attrs={"type": "date"})
) )
probability = forms.IntegerField( car = forms.ModelChoiceField(
label=_("Probability (%)"), queryset=Car.objects.all(), label=_("Car"), required=True
widget=forms.NumberInput(
attrs={
"type": "range",
"min": "0",
"max": "100",
"step": "1",
"class": "form-range",
"oninput": "this.nextElementSibling.value = this.value",
}
),
initial=50, # Default value
) )
class Meta: class Meta:
@ -1280,23 +1271,17 @@ class OpportunityForm(forms.ModelForm):
"lead", "lead",
"car", "car",
"stage", "stage",
"probability",
"amount",
"expected_revenue",
"expected_close_date", "expected_close_date",
] ]
widgets = {
"expected_revenue": forms.NumberInput(attrs={"readonly": "readonly"}),
}
def __init__(self, *args, **kwargs): # def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # super().__init__(*args, **kwargs)
# Add a visible number input to display the current value # # Add a visible number input to display the current value
self.fields["probability"].widget.attrs["class"] = ( # self.fields["probability"].widget.attrs["class"] = (
"d-none" # Hide the default input # "d-none" # Hide the default input
) # )
if self.instance and self.instance.pk: # if self.instance and self.instance.pk:
self.fields["probability"].initial = self.instance.probability # self.fields["probability"].initial = self.instance.probability
class OpportunityStageForm(forms.ModelForm): class OpportunityStageForm(forms.ModelForm):
@ -2255,3 +2240,12 @@ 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):
# Add additional fields for the registration form
class Meta:
model = UserRegistration
fields = ("name","arabic_name", "email","phone_number", "crn", "vrn", "address")

View File

@ -49,7 +49,11 @@ from plans.models import UserPlan
from django.db.models import Q from django.db.models import Q
from imagekit.models import ImageSpecField from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill from imagekit.processors import ResizeToFill
from encrypted_model_fields.fields import (
EncryptedCharField,
EncryptedDateField,
EncryptedEmailField,
)
# from plans.models import AbstractPlan # from plans.models import AbstractPlan
# from simple_history.models import HistoricalRecords # from simple_history.models import HistoricalRecords
from plans.models import Invoice from plans.models import Invoice
@ -1339,7 +1343,7 @@ class Dealer(models.Model, LocalizedNameMixin):
) )
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name")) name = models.CharField(max_length=255, verbose_name=_("English Name"))
phone_number = models.CharField( phone_number = EncryptedCharField(
max_length=255, max_length=255,
verbose_name=_("Phone Number"), verbose_name=_("Phone Number"),
validators=[SaudiPhoneNumberValidator()], validators=[SaudiPhoneNumberValidator()],
@ -1707,18 +1711,18 @@ class Customer(models.Model):
max_length=1, max_length=1,
verbose_name=_("Gender"), verbose_name=_("Gender"),
) )
dob = models.DateField(verbose_name=_("Date of Birth"), null=True, blank=True) dob = EncryptedDateField(verbose_name=_("Date of Birth"), null=True, blank=True)
email = models.EmailField(verbose_name=_("Email")) email = EncryptedEmailField(verbose_name=_("Email"))
national_id = models.CharField( national_id = EncryptedCharField(
max_length=10, unique=True, verbose_name=_("National ID"), null=True, blank=True max_length=10, unique=True, verbose_name=_("National ID"), null=True, blank=True
) )
phone_number = models.CharField( phone_number = EncryptedCharField(
max_length=255, max_length=255,
verbose_name=_("Phone Number"), verbose_name=_("Phone Number"),
validators=[SaudiPhoneNumberValidator()], validators=[SaudiPhoneNumberValidator()],
) )
address = models.CharField( address = EncryptedCharField(
max_length=200, blank=True, null=True, verbose_name=_("Address") max_length=200, blank=True, null=True, verbose_name=_("Address")
) )
active = models.BooleanField(default=True, verbose_name=_("Active")) active = models.BooleanField(default=True, verbose_name=_("Active"))
@ -2294,22 +2298,48 @@ class Schedule(models.Model):
related_name="schedules", related_name="schedules",
null=True, null=True,
blank=True, blank=True,
verbose_name=_("Customer"),
) )
scheduled_by = models.ForeignKey(User, on_delete=models.CASCADE) scheduled_by = models.ForeignKey(User, on_delete=models.CASCADE)
purpose = models.CharField(max_length=200, choices=PURPOSE_CHOICES) purpose = models.CharField(
scheduled_at = models.DateTimeField() max_length=200,
start_time = models.TimeField(verbose_name=_("Start Time"), null=True, blank=True) choices=PURPOSE_CHOICES,
end_time = models.TimeField(verbose_name=_("End Time"), null=True, blank=True) verbose_name=_("Purpose"),
help_text=_("What is the purpose of this schedule?"),
)
scheduled_at = models.DateTimeField(verbose_name=_("Scheduled Date"))
start_time = models.TimeField(
verbose_name=_("Start Time"), null=True, blank=True, help_text=_("HH:MM")
)
end_time = models.TimeField(
verbose_name=_("End Time"), null=True, blank=True, help_text=_("HH:MM")
)
scheduled_type = models.CharField( scheduled_type = models.CharField(
max_length=200, choices=ScheduledType, default="Call" max_length=200,
choices=ScheduledType,
default="Call",
verbose_name=_("Scheduled Type"),
help_text=_("What type of schedule is this?"),
) )
completed = models.BooleanField(default=False, verbose_name=_("Completed")) completed = models.BooleanField(
notes = models.TextField(blank=True, null=True) default=False,
verbose_name=_("Completed"),
help_text=_("Has this schedule been completed?"),
)
notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
status = models.CharField( status = models.CharField(
max_length=200, choices=ScheduleStatusChoices, default="Scheduled" max_length=200,
choices=ScheduleStatusChoices,
default="Scheduled",
verbose_name=_("Status"),
help_text=_("What is the status of this schedule?"),
)
created_at = models.DateTimeField(
auto_now_add=True, verbose_name=_("Created Date"), help_text=_("When was this schedule created?")
)
updated_at = models.DateTimeField(
auto_now=True, verbose_name=_("Updated Date"), help_text=_("When was this schedule last updated?")
) )
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):
return f"Scheduled {self.purpose} on {self.scheduled_at}" return f"Scheduled {self.purpose} on {self.scheduled_at}"
@ -2414,10 +2444,11 @@ class Opportunity(models.Model):
"Lead", "Lead",
related_name="opportunity", related_name="opportunity",
on_delete=models.CASCADE, on_delete=models.CASCADE,
verbose_name=_("Lead"),
null=True, null=True,
blank=True, blank=True,
) )
probability = models.PositiveIntegerField(validators=[validate_probability]) probability = models.PositiveIntegerField(validators=[validate_probability],null=True, blank=True)
amount = models.DecimalField( amount = models.DecimalField(
max_digits=10, max_digits=10,
decimal_places=2, decimal_places=2,
@ -3858,3 +3889,63 @@ 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,null=True,blank=True)
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 .tasks import create_user_dealer
if self.is_created or User.objects.filter(email=self.email).exists():
logger.info(f"Account already created or exists: {self.email}")
return False
password = make_random_password()
try:
logger.info(f"Creating user account {self.email}")
dealer = create_user_dealer(
email=self.email,
password=password,
name=self.name,
arabic_name=self.arabic_name,
phone=self.phone_number,
crn=self.crn,
vrn=self.vrn,
address=self.address
)
if dealer:
self.is_created = True
self.password = password
self.save()
logger.info(f"User account created successfully: {self.email}")
return True
else:
logger.error(f"Failed to create dealer account: {self.email}")
return False
except Exception as e:
logger.error(f"Error creating account for {self.email}: {e}")
return False

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)
@ -310,21 +310,24 @@ def create_item_model(sender, instance, created, **kwargs):
""" """
entity = instance.dealer.entity entity = instance.dealer.entity
if created: coa = entity.get_default_coa()
coa = entity.get_default_coa() uom = entity.get_uom_all().filter(name="Unit").first()
uom = entity.get_uom_all().get(name="Unit") if not uom:
uom = entity.create_uom(name="Unit", unit_abbr="unit")
if not instance.item_model: if not instance.item_model:
inventory = entity.create_item_product( inventory = entity.create_item_product(
name=instance.vin, name=instance.vin,
item_type=ItemModel.ITEM_TYPE_MATERIAL, item_type=ItemModel.ITEM_TYPE_MATERIAL,
uom_model=uom, uom_model=uom,
coa_model=coa, coa_model=coa,
) )
instance.item_model = inventory instance.item_model = inventory
inventory.save() instance.save()
else:
if instance.marked_price:
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 +1389,39 @@ 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 created:
send_email(
settings.DEFAULT_FROM_EMAIL,
instance.email,
"Account Registration",
"""
Thank you for registering with us. We will contact you shortly to complete your application.
شكرا لمراسلتنا. سوف نتصل بك قريبا لاستكمال طلبك.
"""
)
if instance.is_created:
logger.info(f"User account created: {instance.email}, sending email")
send_email(
settings.DEFAULT_FROM_EMAIL,
instance.email,
"Account Created",
f"""
Dear {instance.name},
Your account has been created and you can login with your email {instance.email} and password: {instance.password}.
Please login to the website to complete your profile and start using our services.
Thank you for choosing us.
عزيزي {instance.name},
لقد تم إنشاء حسابك والآن يمكنك تسجيل الدخول باستخدام بريدك الإلكتروني {instance.email} وكلمة المرور: {instance.password}.
يرجى تسجيل الدخول إلى الموقع لاستكمال الملف الشخصي والبدء في استخدام خدماتنا.
شكرا لاختيارك لنا.
""")

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,16 +2,18 @@ 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"),
# Tasks # Tasks
path("legal/", views.terms_and_privacy, name="terms_and_privacy"), path("legal/", views.terms_and_privacy, name="terms_and_privacy"),

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

@ -1440,13 +1440,13 @@ class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.filter(dealer=dealer) qs = qs.filter(dealer=dealer)
# status = self.request.GET.get("status") # status = self.request.GET.get("status")
# status = self.request.GET.get("status")
search = self.request.GET.get("search") search = self.request.GET.get("search")
make = self.request.GET.get("make", None) make = self.request.GET.get("make", None)
model = self.request.GET.get("model", None) model = self.request.GET.get("model", None)
year = self.request.GET.get("year", None) year = self.request.GET.get("year", None)
car_status = self.request.GET.get("car_status", None) car_status = self.request.GET.get("car_status", None)
print("ALLLLLLLLL:::",make,model) print(car_status)
if car_status: if car_status:
qs = qs.filter(status=car_status) qs = qs.filter(status=car_status)
if search: if search:
@ -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")
@ -6330,9 +6331,14 @@ def lead_create(request, dealer_slug):
form.filter_qs(dealer=dealer) form.filter_qs(dealer=dealer)
if make := request.GET.get("id_car_make", None): if make := request.GET.get("id_car_make", None):
form.fields["id_car_model"].queryset = models.CarModel.objects.filter( qs = models.CarModel.objects.filter(
id_car_make=int(make) id_car_make=int(make)
) )
form.fields["id_car_model"].queryset = qs
form.fields["id_car_model"].choices = [
(obj.id_car_model, obj.get_local_name()) for obj in qs
]
else: else:
dealer_make_list = models.DealersMake.objects.filter(dealer=dealer).values_list( dealer_make_list = models.DealersMake.objects.filter(dealer=dealer).values_list(
"car_make", flat=True "car_make", flat=True
@ -6366,7 +6372,11 @@ def lead_create(request, dealer_slug):
] ]
if first_make := qs.first(): if first_make := qs.first():
form.fields["id_car_model"].queryset = first_make.carmodel_set.all() qs = first_make.carmodel_set.all()
form.fields["id_car_model"].queryset = qs
form.fields["id_car_model"].choices = [
(obj.id_car_model, obj.get_local_name()) for obj in qs
]
return render(request, "crm/leads/lead_form.html", {"form": form}) return render(request, "crm/leads/lead_form.html", {"form": form})
@ -11669,6 +11679,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

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,350 +1,162 @@
aiohappyeyeballs annotated-types==0.7.0
aiohttp anyio==4.9.0
aiohttp-retry arrow==1.3.0
aiosignal asgiref==3.9.1
alabaster attrs==25.3.0
albucore autobahn==24.4.2
albumentations Automat==25.4.16
annotated-types Babel==2.15.0
anthropic beautifulsoup4==4.13.4
anyio blessed==1.21.0
arabic-reshaper cattrs==25.1.1
argcomplete certifi==2025.7.9
arrow cffi==1.17.1
asgiref channels==4.2.2
astor charset-normalizer==3.4.2
astroid click==8.2.1
attrs colorama==0.4.6
autopep8 constantly==23.10.4
Babel crispy-bootstrap5==2025.6
beautifulsoup4 cryptography==45.0.5
bleach cssbeautifier==1.15.4
blessed daphne==4.2.1
blinker defusedxml==0.7.1
boto3 diff-match-patch==20241021
botocore distro==1.9.0
Brotli Django==5.2.4
cachetools django-allauth==65.10.0
cattrs django-appconf==1.1.0
certifi django-appointment==3.8.0
cffi django-background-tasks==1.2.8
chardet django-bootstrap5==25.1
charset-normalizer django-ckeditor==6.7.3
click django-cors-headers==4.7.0
cohere django-countries==7.6.1
colorama django-crispy-forms==2.4
commonmark django-debug-toolbar==5.2.0
contourpy django-easy-audit==1.3.7
crispy-bootstrap5 django-encrypted-model-fields==0.6.5
cryptography django-extensions==4.1
cssselect2 django-filter==25.1
ctranslate2 django-imagekit==5.0.0
cycler django-import-export==4.3.8
Cython django-js-asset==3.1.2
dataclasses-json django-ledger==0.7.6.1
decorator django-manager-utils==3.1.5
defusedxml django-next-url-mixin==0.4.0
desert django-ordered-model==3.7.4
diff-match-patch django-phonenumber-field==8.0.0
dill django-picklefield==3.3
distro django-plans==2.0.0
dj-rest-auth django-prometheus==2.4.1
Django django-q2==1.8.0
django-allauth django-query-builder==3.2.0
django-appointment django-schema-graph==3.1.0
django-autoslug django-sequences==3.0
django-background-tasks django-tables2==2.7.5
django-bootstrap5 django-treebeard==4.7.1
django-ckeditor django-widget-tweaks==1.5.0
django-classy-tags djangorestframework==3.16.0
django-cors-headers djhtml==3.0.8
django-countries djlint==1.36.4
django-crispy-forms dnspython==2.7.0
django-debug-toolbar docopt==0.6.2
django-easy-audit EditorConfig==0.17.1
django-extensions Faker==37.4.0
django-filter fleming==0.7.0
django-formtools fonttools==4.58.5
django-import-export fpdf==1.7.2
django-js-asset fpdf2==2.8.3
django-ledger greenlet==3.2.3
django-model-utils gunicorn==23.0.0
django-money h11==0.16.0
django-next-url-mixin h2==4.2.0
django-nine hpack==4.1.0
django-nonefield httpcore==1.0.9
django-ordered-model httpx==0.28.1
django-pdf-actions hyperframe==6.1.0
django-phonenumber-field hyperlink==21.0.0
django-picklefield icalendar==6.3.1
django-plans idna==3.10
django-prometheus incremental==24.7.2
django-q2 iron-core==1.2.1
django-schema-graph iron-mq==0.9
django-sekizai jiter==0.10.0
django-sequences jsbeautifier==1.15.4
django-silk json5==0.12.0
django-simple-history jsonpatch==1.33
django-sms jsonpointer==3.0.0
django-sslserver-v2 jwt==1.4.0
django-tables2 langchain==0.3.26
django-treebeard langchain-core==0.3.68
django-view-breadcrumbs langchain-ollama==0.3.4
django-widget-tweaks langchain-text-splitters==0.3.8
djangocms-admin-style langsmith==0.4.4
djangorestframework luhnchecker==0.0.12
djangorestframework_simplejwt Markdown==3.8.2
djangoviz markdown-it-py==3.0.0
djhtml mdurl==0.1.2
docopt num2words==0.5.14
docutils numpy==2.3.1
easy-thumbnails ofxtools==0.9.5
emoji ollama==0.5.1
et_xmlfile openai==1.93.3
eval_type_backport opencv-python==4.11.0.86
executing orjson==3.10.18
Faker packaging==24.2
fasta2a pandas==2.3.1
fastavro pathspec==0.12.1
filelock phonenumbers==8.13.42
fire pilkit==3.0
fonttools pillow==10.4.0
fpdf priority==1.3.0
fpdf2 prometheus_client==0.22.1
frozenlist psycopg2-binary==2.9.10
fsspec pyasn1==0.6.1
google-auth pyasn1_modules==0.4.2
google-genai pycparser==2.22
googleapis-common-protos pydantic==2.11.7
gprof2dot pydantic_core==2.33.2
graphqlclient Pygments==2.19.2
greenlet pymongo==4.14.1
griffe pyOpenSSL==25.1.0
groq python-dateutil==2.9.0.post0
h11 python-dotenv==1.1.1
h2 python-slugify==8.0.4
hf-xet python-stdnum==2.1
hpack pytz==2025.2
hstspreload pyvin==0.0.2
httpcore PyYAML==6.0.2
httpx pyzbar==0.1.9
httpx-sse redis==6.2.0
huggingface-hub regex==2024.11.6
hyperframe requests==2.32.4
icalendar requests-toolbelt==1.0.0
idna rich==14.0.0
imageio ruff==0.12.2
imagesize service-identity==24.2.0
imgaug setuptools==80.9.0
importlib_metadata six==1.17.0
iso4217 sniffio==1.3.1
isodate soupsieve==2.7
isort SQLAlchemy==2.0.41
itsdangerous sqlparse==0.5.3
Jinja2 suds==1.2.0
jiter swapper==1.3.0
jmespath tablib==3.8.0
joblib tenacity==9.1.2
jsonpatch text-unidecode==1.3
jsonpointer tqdm==4.67.1
jwt Twisted==25.5.0
kiwisolver txaio==25.6.1
langchain types-python-dateutil==2.9.0.20250708
langchain-community typing-inspection==0.4.1
langchain-core typing_extensions==4.14.1
langchain-ollama tzdata==2025.2
langchain-text-splitters urllib3==2.5.0
langsmith uvicorn==0.35.0
lazy_loader uvicorn-worker==0.3.0
ledger wcwidth==0.2.13
libretranslatepy whitenoise==6.9.0
lmdb zope.interface==7.2
logfire zstandard==0.23.0
logfire-api
luhnchecker
lxml
Markdown
markdown-it-py
MarkupSafe
marshmallow
matplotlib
mccabe
mcp
mdurl
mistralai
MouseInfo
mpmath
multidict
mypy_extensions
networkx
newrelic
nltk
num2words
numpy
oauthlib
ofxtools
ollama
openai
opencv-contrib-python
opencv-python
opencv-python-headless
openpyxl
opentelemetry-api
opentelemetry-exporter-otlp-proto-common
opentelemetry-exporter-otlp-proto-http
opentelemetry-instrumentation
opentelemetry-proto
opentelemetry-sdk
opentelemetry-semantic-conventions
opt_einsum
orjson
outcome
packaging
pandas
pango
pdfkit
phonenumbers
pillow
platformdirs
prometheus_client
prompt_toolkit
propcache
protobuf
psycopg
psycopg-binary
psycopg-c
psycopg2-binary
py-moneyed
pyasn1
pyasn1_modules
PyAutoGUI
pyclipper
pycodestyle
pycparser
pydantic
pydantic-ai
pydantic-ai-slim
pydantic-evals
pydantic-graph
pydantic-settings
pydantic_core
pydotplus
pydyf
PyGetWindow
Pygments
PyJWT
pylint
PyMsgBox
PyMySQL
pyobjc-core
pyobjc-framework-Cocoa
pyobjc-framework-Quartz
pyparsing
pypdf
pyperclip
pyphen
pypng
PyRect
PyScreeze
pyserial
PySocks
python-bidi
python-dateutil
python-docx
python-dotenv
python-multipart
python-openid
python-slugify
python-stdnum
python3-saml
pytweening
pytz
pyvin
pywa
pywhat
pywhatkit
PyYAML
pyzbar
qrcode
RapidFuzz
redis
regex
reportlab
requests
requests-oauthlib
requests-toolbelt
rfc3986
rich
rsa
rubicon-objc
s3transfer
sacremoses
safetensors
scikit-image
scikit-learn
scipy
selenium
sentence-transformers
sentencepiece
shapely
simsimd
six
slugify
sniffio
snowballstemmer
sortedcontainers
soupsieve
SQLAlchemy
sqlparse
sse-starlette
stanza
starlette
stringzilla
suds
swapper
sympy
tablib
tenacity
termcolor
text-unidecode
threadpoolctl
tifffile
tinycss2
tinyhtml5
tokenizers
tomli
tomlkit
torch
tqdm
transformers
trio
trio-websocket
twilio
types-python-dateutil
types-requests
typing-inspect
typing-inspection
typing_extensions
tzdata
Unidecode
upgrade-requirements
urllib3
uvicorn
vin
vininfo
vishap
vpic-api
wcwidth
weasyprint
webencodings
websocket-client
websockets
Werkzeug
wikipedia
wrapt
wsproto
xmlsec
yarl
zipp
zopfli
zstandard

161
requirements_prod.txt Normal file
View File

@ -0,0 +1,161 @@
annotated-types==0.7.0
anyio==4.9.0
arrow==1.3.0
asgiref==3.9.1
attrs==25.3.0
autobahn==24.4.2
Automat==25.4.16
Babel==2.15.0
beautifulsoup4==4.13.4
blessed==1.21.0
cattrs==25.1.1
certifi==2025.7.9
cffi==1.17.1
channels==4.2.2
charset-normalizer==3.4.2
click==8.2.1
colorama==0.4.6
constantly==23.10.4
crispy-bootstrap5==2025.6
cryptography==45.0.5
cssbeautifier==1.15.4
daphne==4.2.1
defusedxml==0.7.1
diff-match-patch==20241021
distro==1.9.0
Django==5.2.4
django-allauth==65.10.0
django-appconf==1.1.0
django-appointment==3.8.0
django-background-tasks==1.2.8
django-bootstrap5==25.1
django-ckeditor==6.7.3
django-cors-headers==4.7.0
django-countries==7.6.1
django-crispy-forms==2.4
django-debug-toolbar==5.2.0
django-easy-audit==1.3.7
django-extensions==4.1
django-filter==25.1
django-imagekit==5.0.0
django-import-export==4.3.8
django-js-asset==3.1.2
django-ledger==0.7.6.1
django-manager-utils==3.1.5
django-next-url-mixin==0.4.0
django-ordered-model==3.7.4
django-phonenumber-field==8.0.0
django-picklefield==3.3
django-plans==2.0.0
django-prometheus==2.4.1
django-q2==1.8.0
django-query-builder==3.2.0
django-schema-graph==3.1.0
django-sequences==3.0
django-tables2==2.7.5
django-treebeard==4.7.1
django-widget-tweaks==1.5.0
djangorestframework==3.16.0
djhtml==3.0.8
djlint==1.36.4
dnspython==2.7.0
docopt==0.6.2
EditorConfig==0.17.1
Faker==37.4.0
fleming==0.7.0
fonttools==4.58.5
fpdf==1.7.2
fpdf2==2.8.3
greenlet==3.2.3
gunicorn==23.0.0
h11==0.16.0
h2==4.2.0
hpack==4.1.0
httpcore==1.0.9
httpx==0.28.1
hyperframe==6.1.0
hyperlink==21.0.0
icalendar==6.3.1
idna==3.10
incremental==24.7.2
iron-core==1.2.1
iron-mq==0.9
jiter==0.10.0
jsbeautifier==1.15.4
json5==0.12.0
jsonpatch==1.33
jsonpointer==3.0.0
jwt==1.4.0
langchain==0.3.26
langchain-core==0.3.68
langchain-ollama==0.3.4
langchain-text-splitters==0.3.8
langsmith==0.4.4
luhnchecker==0.0.12
Markdown==3.8.2
markdown-it-py==3.0.0
mdurl==0.1.2
num2words==0.5.14
numpy==2.3.1
ofxtools==0.9.5
ollama==0.5.1
openai==1.93.3
opencv-python==4.11.0.86
orjson==3.10.18
packaging==24.2
pandas==2.3.1
pathspec==0.12.1
phonenumbers==8.13.42
pilkit==3.0
pillow==10.4.0
priority==1.3.0
prometheus_client==0.22.1
psycopg2-binary==2.9.10
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
pydantic==2.11.7
pydantic_core==2.33.2
Pygments==2.19.2
pymongo==4.14.1
pyOpenSSL==25.1.0
python-dateutil==2.9.0.post0
python-dotenv==1.1.1
python-slugify==8.0.4
python-stdnum==2.1
pytz==2025.2
pyvin==0.0.2
PyYAML==6.0.2
pyzbar==0.1.9
redis==6.2.0
regex==2024.11.6
requests==2.32.4
requests-toolbelt==1.0.0
rich==14.0.0
ruff==0.12.2
service-identity==24.2.0
setuptools==80.9.0
six==1.17.0
sniffio==1.3.1
soupsieve==2.7
SQLAlchemy==2.0.41
sqlparse==0.5.3
suds==1.2.0
swapper==1.3.0
tablib==3.8.0
tenacity==9.1.2
text-unidecode==1.3
tqdm==4.67.1
Twisted==25.5.0
txaio==25.6.1
types-python-dateutil==2.9.0.20250708
typing-inspection==0.4.1
typing_extensions==4.14.1
tzdata==2025.2
urllib3==2.5.0
uvicorn==0.35.0
uvicorn-worker==0.3.0
wcwidth==0.2.13
whitenoise==6.9.0
zope.interface==7.2
zstandard==0.23.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

@ -77,7 +77,7 @@
</div> </div>
</div> </div>
</section> </section>
{% include 'footer.html' %}
{% if LOGIN_BY_CODE_ENABLED or PASSKEY_LOGIN_ENABLED %} {% if LOGIN_BY_CODE_ENABLED or PASSKEY_LOGIN_ENABLED %}
<hr> <hr>
{% element button_group vertical=True %} {% element button_group vertical=True %}

View File

@ -1,8 +1,9 @@
{% 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-4">
<div class="container" style="max-width:60rem;"> <div class="container" style="max-width:60rem;">
<div class="row form-container" id="form-container"> <div class="row form-container" id="form-container">
<div class="col-12 "> <div class="col-12 ">
@ -20,292 +21,49 @@
</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>
<button class="btn btn-primary" type="submit">{% trans 'Sign Up' %}</button>
</div> </div>
<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 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-lg btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Our Refund Policy {% trans "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="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered"> <div class="modal-dialog modal-xl">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="contentModalLabel">Refund Policy</h5> <h5 class="modal-title" id="exampleModalLabel">{% trans "Our Refund Policy" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Loading...</p> {% include 'haikal_policy/refund_policy.html' %}
</div> </div>
</div>
</div> </div>
</div> </div>
</div>
{% include 'footer.html' %}
<script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %} {% endblock content %}
{% block customJS %} {% block customJS %}
<script src="{% static 'js/phoenix.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script> <script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script> <script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<script type="module" <script type="module"
@ -406,37 +164,5 @@
} }
//modal for policy
document.addEventListener('DOMContentLoaded', function() {
const contentModal = document.getElementById('contentModal');
if (contentModal) {
contentModal.addEventListener('show.bs.modal', function(event) {
// Get the button that triggered the modal
const button = event.relatedTarget;
// Extract the URL from the button's data-url attribute
const url = button.getAttribute('data-url');
// Select the modal body element
const modalBody = contentModal.querySelector('.modal-body');
// Use the Fetch API to load content
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
})
.then(html => {
// Insert the HTML content into the modal body
modalBody.innerHTML = html;
})
.catch(error => {
modalBody.innerHTML = `<p class="text-danger">Failed to load content: ${error.message}</p>`;
});
});
}
});
</script> </script>
{% endblock customJS %} {% endblock customJS %}

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

@ -282,7 +282,7 @@
type="button" type="button"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#exampleModal"> data-bs-target="#exampleModal">
<i class="fa-solid fa-user-plus me-2"></i> Reassign Lead <i class="fa-solid fa-user-plus me-2"></i> {%trans "Reassign Lead"%}
</button> </button>
{% endif %} {% endif %}
<button class="btn btn-phoenix-primary btn-sm" <button class="btn btn-phoenix-primary btn-sm"
@ -301,7 +301,7 @@
method="post"> method="post">
{% csrf_token %} {% csrf_token %}
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Reassign Lead To Another Employee</h5> <h5 class="modal-title" id="exampleModalLabel">{%trans "Reassign Lead To Another Employee"%}</h5>
<button class="btn btn-close p-1" <button class="btn btn-close p-1"
type="button" type="button"
data-bs-dismiss="modal" data-bs-dismiss="modal"
@ -581,25 +581,25 @@
scope="col" scope="col"
data-sort="subject" data-sort="subject"
style="width:31%; style="width:31%;
min-width:350px">Subject</th> min-width:350px">{{ _("Subject") }}</th>
<th class="sort align-middle pe-3 text-uppercase" <th class="sort align-middle pe-3 text-uppercase"
scope="col" scope="col"
data-sort="sent" data-sort="sent"
style="width:15%; style="width:15%;
min-width:130px">Sent by</th> min-width:130px">{{ _("Sent by") }}</th>
<th class="sort align-middle text-start text-uppercase" <th class="sort align-middle text-start text-uppercase"
scope="col" scope="col"
data-sort="date" data-sort="date"
style="min-width:165px">Date</th> style="min-width:165px">{{ _("Date") }}</th>
<th class="sort align-middle pe-0 text-uppercase" <th class="sort align-middle pe-0 text-uppercase"
scope="col" scope="col"
style="width:15%; style="width:15%;
min-width:100px">Action</th> min-width:100px">{{ _("Action") }}</th>
<th class="sort align-middle text-end text-uppercase" <th class="sort align-middle text-end text-uppercase"
scope="col" scope="col"
data-sort="status" data-sort="status"
style="width:15%; style="width:15%;
min-width:100px">Status</th> min-width:100px">{{ _("Status") }}</th>
</tr> </tr>
</thead> </thead>
<tbody class="list" id="all-email-table-body"> <tbody class="list" id="all-email-table-body">
@ -619,10 +619,10 @@
<td class="sent align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2">{{ email.from_email }}</td> <td class="sent align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2">{{ email.from_email }}</td>
<td class="date align-middle white-space-nowrap text-body py-2">{{ email.created|naturalday }}</td> <td class="date align-middle white-space-nowrap text-body py-2">{{ email.created|naturalday }}</td>
<td class="align-middle white-space-nowrap ps-3"> <td class="align-middle white-space-nowrap ps-3">
<a class="text-body" href=""><span class="fa-solid fa-phone text-primary me-2"></span>Call</a> <a class="text-body" href=""><span class="fa-solid fa-phone text-primary me-2"></span>{%trans "Call"%</a>
</td> </td>
<td class="status align-middle fw-semibold text-end py-2"> <td class="status align-middle fw-semibold text-end py-2">
<span class="badge badge-phoenix fs-10 badge-phoenix-success">sent</span> <span class="badge badge-phoenix fs-10 badge-phoenix-success">{%trans "sent"%}</span>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -670,25 +670,25 @@
scope="col" scope="col"
data-sort="subject" data-sort="subject"
style="width:31%; style="width:31%;
min-width:350px">Subject</th> min-width:350px">{% trans "Subject" %}</th>
<th class="sort align-middle pe-3 text-uppercase" <th class="sort align-middle pe-3 text-uppercase"
scope="col" scope="col"
data-sort="sent" data-sort="sent"
style="width:15%; style="width:15%;
min-width:130px">Sent by</th> min-width:130px">{% trans "Sent by" %}</th>
<th class="sort align-middle text-start text-uppercase" <th class="sort align-middle text-start text-uppercase"
scope="col" scope="col"
data-sort="date" data-sort="date"
style="min-width:165px">Date</th> style="min-width:165px">{% trans "Date" %}</th>
<th class="sort align-middle pe-0 text-uppercase" <th class="sort align-middle pe-0 text-uppercase"
scope="col" scope="col"
style="width:15%; style="width:15%;
min-width:100px">Action</th> min-width:100px">{% trans "Action" %}</th>
<th class="sort align-middle text-end text-uppercase" <th class="sort align-middle text-end text-uppercase"
scope="col" scope="col"
data-sort="status" data-sort="status"
style="width:15%; style="width:15%;
min-width:100px">Status</th> min-width:100px">{% trans "Status" %}</th>
</tr> </tr>
</thead> </thead>
<tbody class="list" id="drafts-email-table-body"> <tbody class="list" id="drafts-email-table-body">
@ -777,20 +777,20 @@
scope="col" scope="col"
data-sort="subject" data-sort="subject"
style="width:31%; style="width:31%;
min-width:100px">Name</th> min-width:100px">{%trans "Name"%}</th>
<th class="sort align-middle pe-3 text" <th class="sort align-middle pe-3 text"
scope="col" scope="col"
data-sort="sent" data-sort="sent"
style="width:15%; style="width:15%;
min-width:400px">Note</th> min-width:400px">{%trans "Note"%}</th>
<th class="sort align-middle text-start" <th class="sort align-middle text-start"
scope="col" scope="col"
data-sort="date" data-sort="date"
style="min-width:100px">Due Date</th> style="min-width:100px">{%trans "Due Date"%}</th>
<th class="sort align-middle text-start" <th class="sort align-middle text-start"
scope="col" scope="col"
data-sort="date" data-sort="date"
style="min-width:100px">Completed</th> style="min-width:100px">{%trans "Completed"%}</th>
</tr> </tr>
</thead> </thead>
<tbody class="list taskTable" id="all-tasks-table-body"> <tbody class="list taskTable" id="all-tasks-table-body">
@ -802,9 +802,7 @@
</div> </div>
<div class="row align-items-center justify-content-between py-2 pe-0 fs-9 mt-3"> <div class="row align-items-center justify-content-between py-2 pe-0 fs-9 mt-3">
<div class="col-auto d-flex"> <div class="col-auto d-flex">
<a class="nav-link px-3 d-block"
href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("View in Calendar") }}
</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -62,7 +62,7 @@
{% if form.stage.errors %}<div class="invalid-feedback d-block">{{ form.stage.errors }}</div>{% endif %} {% if form.stage.errors %}<div class="invalid-feedback d-block">{{ form.stage.errors }}</div>{% endif %}
</div> </div>
<!-- Amount Field --> <!-- Amount Field -->
<div class="mb-4"> {% comment %} <div class="mb-4">
<label class="form-label" for="{{ form.amount.id_for_label }}"> <label class="form-label" for="{{ form.amount.id_for_label }}">
{{ form.amount.label }} {{ form.amount.label }}
<span class="text-danger">*</span> <span class="text-danger">*</span>
@ -72,9 +72,9 @@
{{ form.amount|add_class:"form-control" }} {{ form.amount|add_class:"form-control" }}
</div> </div>
{% if form.amount.errors %}<div class="invalid-feedback d-block">{{ form.amount.errors }}</div>{% endif %} {% if form.amount.errors %}<div class="invalid-feedback d-block">{{ form.amount.errors }}</div>{% endif %}
</div> </div> {% endcomment %}
<!-- Probability Field --> <!-- Probability Field -->
<div class="mb-4"> {% comment %} <div class="mb-4">
<label class="form-label" for="{{ form.probability.id_for_label }}"> <label class="form-label" for="{{ form.probability.id_for_label }}">
{{ form.probability.label }} {{ form.probability.label }}
<span class="text-danger">*</span> <span class="text-danger">*</span>
@ -106,10 +106,10 @@
{% if form.expected_revenue.errors %} {% if form.expected_revenue.errors %}
<div class="invalid-feedback d-block">{{ form.expected_revenue.errors }}</div> <div class="invalid-feedback d-block">{{ form.expected_revenue.errors }}</div>
{% endif %} {% endif %}
</div> </div> {% endcomment %}
<!-- Closing Date --> <!-- Closing Date -->
<div class="mb-5"> <div class="mb-5">
<label class="form-label" for="{{ form.closing_date.id_for_label }}">{{ form.closing_date.label }}</label> <label class="form-label" for="{{ form.expected_close_date.id_for_label }}">{{ form.expected_close_date.label }}</label>
<div class="input-group"> <div class="input-group">
{{ form.expected_close_date|add_class:"form-control" }} {{ form.expected_close_date|add_class:"form-control" }}
<span class="input-group-text"><span class="far fa-calendar"></span></span> <span class="input-group-text"><span class="far fa-calendar"></span></span>

View File

@ -75,7 +75,7 @@
{% block content %} {% block content %}
<div class="container mt-4"> <div class="container mt-4">
<h2> <h2>
Upload Cars CSV <i class="fa-solid fa-file-csv text-primary"></i> {% trans 'Upload Cars CSV' %} <i class="fa-solid fa-file-csv text-primary"></i>
</h2> </h2>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<a href="{% static 'sample/cars_sample.csv' %}" <a href="{% static 'sample/cars_sample.csv' %}"

View File

@ -1,16 +1,4 @@
{% load i18n %} {% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans "Haikal Refund Policy" %}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css">
</head>
<body>
<div class="policy-container m-4"> <div class="policy-container m-4">
<div class="policy-header"> <div class="policy-header">
@ -80,7 +68,3 @@
</p> </p>
</section> </section>
</div> </div>
</body>
</html>

View File

@ -488,30 +488,15 @@
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="navbar-logo"> <div class="navbar-logo">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
{% with name_to_display=request.user.staff.first_name|default:request.dealer.name %} {% with name_to_display=request.user.first_name|default:request.dealer.name %}
<h6 class="text-gray-600 ms-2 d-none d-sm-block fs-8" <h6 class="text-gray-600 ms-2 d-none d-sm-block fs-8"
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
data-bs-placement="bottom" data-bs-placement="bottom"
title="{% trans 'Logged in as ' %}{{ request.user.username }}"> title="{% trans 'Logged in as ' %}{{ request.user.username }}">
{% trans 'Hello, ' %}{{ name_to_display }} {% trans 'Hello, ' %}{{ name_to_display }}
</h6> </h6>
{% endwith %} {% endwith %}
</div> </div>
</div>
<div class="navbar-logo ">
<div class="d-flex align-items-center">
<small class="text-gray-600 ms-2 d-none d-sm-block fs-9"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
>
{% now "l, F j, Y g:i A" %}
</small>
</div>
</div> </div>
{% endif %} {% endif %}
<ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false"> <ul class="navbar-nav navbar-nav-icons flex-row gap-2" hx-boost="false">
@ -665,19 +650,19 @@
</hr> </hr>
<div class="px-3"> <div class="px-3">
<a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100" <a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100"
href="{% url 'account_logout' %}"> <i class="fa-solid fa-right-from-bracket me-2"></i></span>{% trans 'Sign Out' %}</a> href="{% url 'account_logout' %}"> <span class="fas fa-power-off me-2"></span>{% trans 'Sign Out' %}</a>
</div> </div>
<div class="my-2 text-center fw-bold fs-10 text-body-quaternary"> <div class="my-2 text-center fw-bold fs-10 text-body-quaternary">
<a class="text-body-quaternary me-1" href="">{% trans 'Privacy policy' %}</a>&bull;<a class="text-body-quaternary mx-1" href="">{% trans 'Terms' %}</a>&bull;<a class="text-body-quaternary ms-1" href="">{% trans "Cookies" %}</a> <a class="text-body-quaternary me-1" href="">{% trans 'Privacy policy' %}</a>&bull;<a class="text-body-quaternary mx-1" href="">{% trans 'Terms' %}</a>&bull;<a class="text-body-quaternary ms-1" href="">Cookies</a>
</div> </div>
{% else %} {% else %}
<div class="px-3"> <div class="px-3">
<a class="btn btn-phoenix-succes d-flex flex-center w-100" <a class="btn btn-phoenix-succes d-flex flex-center w-100"
href="{% url 'account_login' %}"> <i class="fa-solid fa-right-to-bracket me-2"></i>{% trans 'Sign In' %}</a> href="{% url 'account_login' %}"> <span class="me-2" data-feather="log-in"></span>{% trans 'Sign In' %}</a>
</div> </div>
<div class="px-3"> <div class="px-3">
<a class="btn btn-phoenix-primary d-flex flex-center w-100" <a class="btn btn-phoenix-primary d-flex flex-center w-100"
href="{% url 'account_signup' %}"> <i class="fa-solid fa-user-plus"></i>{% trans 'Sign Up' %}</a> href="{% url 'account_signup' %}"> <span class="me-2" data-feather="user-plus"></span>{% trans 'Sign Up' %}</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -194,11 +194,12 @@
{% if perms.inventory.add_carregistration %} {% if perms.inventory.add_carregistration %}
<button type="button" <button type="button"
class="btn btn-sm btn-phoenix-success" class="btn btn-sm btn-phoenix-success"
data-bs-toggle="modal"
data-bs-target="#mainModal"
hx-get="{% url 'add_registration' request.dealer.slug car.slug %}" hx-get="{% url 'add_registration' request.dealer.slug car.slug %}"
hx-target=".main-modal-body" hx-target=".main-modal-body"
hx-swap="innerHTML">{% trans 'Add' %}</button> hx-swap="innerHTML"
data-bs-toggle="modal"
data-bs-target="#mainModal"
>{% trans 'Add' %}</button>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -214,10 +215,10 @@
{{ car.location.showroom.get_local_name }} {{ car.location.showroom.get_local_name }}
{% endif %} {% endif %}
{% if perms.inventory.add_cartransfer %} {% if perms.inventory.add_cartransfer %}
<a href="{% url 'update_car_location' car.slug car.location.pk %}" {% comment %} <a href="{% url 'update_car_location' car.slug car.location.pk %}"
class="btn btn-phoenix-danger btn-sm"> class="btn btn-phoenix-danger btn-sm">
{% trans "transfer"|capfirst %} {% trans "transfer"|capfirst %}
</a> </a> {% endcomment %}
{% endif %} {% endif %}
{% else %} {% else %}
{% trans "No location available." %} {% trans "No location available." %}

View File

@ -25,7 +25,7 @@
</style> </style>
{% endblock customCSS %} {% endblock customCSS %}
{% block content %} {% block content %}
{% if cars or request.GET.q %}
<div class="container-fluid" id="projectSummary"> <div class="container-fluid" id="projectSummary">
<div class="row g-3 justify-content-between align-items-end mb-4"> <div class="row g-3 justify-content-between align-items-end mb-4">
<div class="col-12 col-sm-auto"> <div class="col-12 col-sm-auto">
@ -105,7 +105,7 @@
<input class="form-control search-input search" <input class="form-control search-input search"
name="search" name="search"
type="search" type="search"
placeholder="Search" placeholder="{% trans 'Search' %}"
aria-label="Search" aria-label="Search"
hx-get="{% url 'car_list' request.dealer.slug %}" hx-get="{% url 'car_list' request.dealer.slug %}"
hx-trigger="keyup changed delay:500ms" hx-trigger="keyup changed delay:500ms"
@ -169,17 +169,17 @@
hx-get="{% url 'car_list' request.dealer.slug %}" hx-get="{% url 'car_list' request.dealer.slug %}"
hx-include=".make,.model,.year,.car_status" hx-include=".make,.model,.year,.car_status"
hx-indicator=".htmx-indicator" hx-indicator=".htmx-indicator"
hx-target=".table-responsive" hx-target=".table-responsive1"
hx-select=".table-responsive" hx-select=".table-responsive1"
hx-swap="outerHTML show:window:top" hx-swap="outerHTML show:window:top"
class="btn btn-sm btn-phoenix-primary ms-1" class="btn btn-sm btn-phoenix-primary ms-1"
hx-on::before-request="filter_before_request()" hx-on::before-request="filter_before_request()"
hx-on::after-request="filter_after_request()">{{ _("Search") }}</button> hx-on::after-request="filter_after_request()">{{ _("Search") }}</button>
</div> </div>
<div class="table-responsive scrollbar"> <div class="table-responsive1 scrollbar">
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9"> <div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9">
<div class="d-flex" <div class="d-flex"
hx-boost="true" hx-boost="false"
hx-push-url="false" hx-push-url="false"
hx-include=".make,.model,.year,.car_status" hx-include=".make,.model,.year,.car_status"
hx-target=".table-responsive" hx-target=".table-responsive"
@ -301,10 +301,6 @@
</div> </div>
</div> </div>
</div> </div>
{% else %}
{% url "car_add" request.dealer.slug as create_car_url %}
{% include "empty-illustration-page.html" with value="car" url=create_car_url %}
{% endif %}
{% endblock %} {% endblock %}
{% block customJS%} {% block customJS%}
<script> <script>

View File

@ -169,7 +169,7 @@
class="form-control form-control-sm" class="form-control form-control-sm"
required required
placeholder="email@example.com" placeholder="email@example.com"
value="{{ request.dealer.email }}"> value="{{ request.dealer.user.email }}">
</div> </div>
<label class="form-label" for="phone">{{ _("Phone Number") }}</label> <label class="form-label" for="phone">{{ _("Phone Number") }}</label>
<div class="input-group"> <div class="input-group">

View File

@ -1,5 +1,15 @@
{% load i18n %}
<div class="form-group" id="form-{{ name }}"> <div class="form-group" id="form-{{ name }}">
<label for="{{ name }}">{{ name|capfirst }}</label> {% if name == "make" %}
<label for="{{ name }}">{% trans "Make" %}</label>
{% elif name == "model" %}
<label for="{{ name }}">{% trans "Model" %}</label>
{% elif name == "serie" %}
<label for="{{ name }}">{% trans "Serie" %}</label>
{% elif name == "trim" %}
<label for="{{ name }}">{% trans "Trim" %}</label>
{% endif %}
<select class="form-control" <select class="form-control"
name="{{ name }}" name="{{ name }}"
id="{{ name }}" id="{{ name }}"

View File

@ -31,7 +31,7 @@
<thead> <thead>
<tr class="bg-body-highlight"> <tr class="bg-body-highlight">
<th scope="col">{% trans "Name" %}</th> <th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Quatnity" %}</th> <th scope="col">{% trans "Quantity" %}</th>
<th scope="col">{% trans "Unit Cost" %}</th> <th scope="col">{% trans "Unit Cost" %}</th>
<th scope="col">{% trans "Is Data Uploaded ?" %}</th> <th scope="col">{% trans "Is Data Uploaded ?" %}</th>
</tr> </tr>

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 %}

View File

@ -10,7 +10,7 @@
</style> </style>
{% endblock extraCSS %} {% endblock extraCSS %}
{% block content %} {% block content %}
<div class="row"> <div class="row d-flex justify-content-center">
<div class="booking-hero-header d-flex align-items-center"> <div class="booking-hero-header d-flex align-items-center">
<div class="bg-holder overlay bg-opacity-75" <div class="bg-holder overlay bg-opacity-75"
style="background-image:url({% static 'video/image-haikal-02.png' %})"> style="background-image:url({% static 'video/image-haikal-02.png' %})">
@ -36,11 +36,10 @@
</div> </div>
</div> </div>
<section class="pt-6 pt-md-10 pb-10" id="feature"> <section class="pt-6 pt-md-10 pb-10" id="feature">
<div class="container-medium"> <div class="bg-holder d-none d-xl-block"
<div class="bg-holder d-none d-xl-block" style="background-image:url({% static 'images/bg/bg-left-27.png' %});
style="background-image:url({% static 'images/bg/bg-left-27.png' %}); background-size:auto;
background-size:auto; background-position:left"></div>
background-position:left"></div>
<div class="bg-holder d-none d-xl-block" <div class="bg-holder d-none d-xl-block"
style="background-image:url({% static 'images/bg/bg-right-27.png' %}); style="background-image:url({% static 'images/bg/bg-right-27.png' %});
background-size:auto; background-size:auto;
@ -128,9 +127,7 @@
<h2 class="mb-7">{{ _("Pricing") }}</h2> <h2 class="mb-7">{{ _("Pricing") }}</h2>
<div class="row g-3 mb-7 mb-lg-11"> <div class="row g-3 mb-7 mb-lg-11">
{% for plan in plan_list %} {% for plan in plan_list %}
<div class="col-lg-4" onclick="window.location='{% url "account_signup" %}';"> <div class="col-lg-4" onclick="window.location='{% url "account_signup" %}';">
<input type="radio" <input type="radio"
class="btn-check" class="btn-check"
name="selected_plan" name="selected_plan"
@ -144,7 +141,7 @@
<h3 class="h6 mb-3"> <h3 class="h6 mb-3">
{{ plan.planpricing_set.first.price }} {{ plan.planpricing_set.first.price }}
<span class="icon-saudi_riyal"></span> <span class="icon-saudi_riyal"></span>
<span class="fs-8 fw-normal">/ {{ _("Per month") }}</span> <span class="fs-8 fw-normal">/{{plan.planpricing_set.first.pricing.period}} {{ _("month") }}</span>
</h3> </h3>
<h5 class="mb-3 h6">{{ _("Included") }}</h5> <h5 class="mb-3 h6">{{ _("Included") }}</h5>
<ul class="fa-ul ps-3 m-0"> <ul class="fa-ul ps-3 m-0">
@ -158,9 +155,7 @@
</div> </div>
</div> </div>
</label> </label>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -238,8 +233,6 @@
</div> </div>
</div> </div>
</section> </section>
<section class="pt-lg-0 pt-xl-8">
{% include 'footer.html' %}
</section>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -43,9 +43,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap" <link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&display=swap"
rel="stylesheet"> rel="stylesheet">
{% comment %} <link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet"> {% endcomment %}
{% comment %} <link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet"> {% endcomment %}
{% comment %} <link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css"> {% endcomment %}
{% if LANGUAGE_CODE == 'ar' %} {% if LANGUAGE_CODE == 'ar' %}
<link href="{% static 'css/theme-rtl.min.css' %}" <link href="{% static 'css/theme-rtl.min.css' %}"
type="text/css" type="text/css"
@ -67,23 +65,22 @@
{% endif %} {% endif %}
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script> <script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script> <script src="{% static 'js/config.js' %}"></script>
{% comment %} <script src="{% static 'js/sweetalert2.all.min.js' %}"></script> {% endcomment %}
<link href="{% static 'css/custom.css' %}" rel="stylesheet"> <link href="{% static 'css/custom.css' %}" rel="stylesheet">
{% block extraCSS %}
{% endblock extraCSS %}
</head> </head>
<body> <body class="d-flex flex-column min-vh-100">
{% include 'welcome_header.html' %} {% include 'welcome_header.html' %}
{% block content %} <main class="flex-grow-1">
{% endblock content %} {% block content %}
{% comment %} {% endblock content %}
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script> </main>
<script src="{% static 'vendors/is/is.min.js' %}"></script>
<script src="{% static 'vendors/list.js/list.min.js' %}"></script> <div class="site-footer mt-auto">
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script> {% include 'welcome_footer.html' %}
<script src="{% static 'vendors/echarts/echarts.min.js' %}"></script> </div>
<script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script> {% endcomment %}
<script src="{% static 'vendors/popper/popper.min.js' %}"></script> <script src="{% static 'vendors/popper/popper.min.js' %}"></script>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> <script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script> <script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
@ -91,22 +88,7 @@
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script> <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/typed.js/typed.umd.js' %}"></script> <script src="{% static 'vendors/typed.js/typed.umd.js' %}"></script>
<script src="{% static 'js/phoenix.js' %}"></script> <script src="{% static 'js/phoenix.js' %}"></script>
{% comment %} <script src="{% static 'vendors/popper/popper.min.js' %}"></script>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script>
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
<script src="{% static 'js/phoenix.js' %}"></script>
<script src="{% static 'vendors/echarts/echarts.min.js' %}"></script>
<script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
<script src="{% static 'vendors/typed.js/typed.umd.js' %}"></script> {% endcomment %}
{% block customJS %} {% block customJS %}
{% endblock customJS %} {% endblock customJS %}
</body> </body>

View File

@ -1,5 +1,5 @@
<section class="footer pb-6 pb-md-11 pt-15"> <section class="footer pb-6 pb-md-11 pt-5">
<div class="container-medium"> <div class="container-medium mt-3">
<div class="row gy-3 justify-content-between"> <div class="row gy-3 justify-content-between">
<div class="row g-0 justify-content-between align-items-center h-100"> <div class="row g-0 justify-content-between align-items-center h-100">
<div class="col-12 col-sm-auto text-center"> <div class="col-12 col-sm-auto text-center">