fix the plans upgrade and more

This commit is contained in:
ismail 2025-07-29 13:18:00 +03:00
parent 8d4d2bff28
commit f117feee6d
16 changed files with 356 additions and 637 deletions

View File

@ -16,7 +16,7 @@ from django_ledger.forms.invoice import (
) )
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase
from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
from django_ledger.forms.journal_entry import ( from django_ledger.forms.journal_entry import (
JournalEntryModelCreateForm as JournalEntryModelCreateFormBase, JournalEntryModelCreateForm as JournalEntryModelCreateFormBase,
) )
@ -2102,3 +2102,14 @@ class VatRateForm(forms.ModelForm):
class Meta: class Meta:
model = VatRate model = VatRate
fields = ["rate"] fields = ["rate"]
class CustomSetPasswordForm(SetPasswordForm):
new_password1 = forms.CharField(
label="New Password",
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'New Password'})
)
new_password2 = forms.CharField(
label="Confirm New Password",
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Confirm New Password'})
)

View File

@ -1,3 +1,4 @@
from datetime import datetime, timedelta
from decimal import Decimal from decimal import Decimal
from django.urls import reverse from django.urls import reverse
from inventory.tasks import create_coa_accounts, create_make_accounts from inventory.tasks import create_coa_accounts, create_make_accounts
@ -24,6 +25,7 @@ from . import models
from django.utils.timezone import now from django.utils.timezone import now
from django.db import transaction from django.db import transaction
from django_q.tasks import async_task from django_q.tasks import async_task
from plans.models import UserPlan
from plans.signals import order_completed, activate_user_plan from plans.signals import order_completed, activate_user_plan
# logging # logging
@ -1212,10 +1214,4 @@ def bill_model_after_approve_notification(sender, instance, created, **kwargs):
kwargs={"dealer_slug": dealer.slug, "entity_slug": dealer.entity.slug, "bill_pk": instance.pk}, kwargs={"dealer_slug": dealer.slug, "entity_slug": dealer.entity.slug, "bill_pk": instance.pk},
), ),
), ),
) )
def handle_upgrade(sender, order, **kwargs):
logger.info(f"User {order.user} upgraded to {order.plan}")
order_completed.connect(handle_upgrade)

View File

@ -1,4 +1,4 @@
import base64
import logging import logging
from plans.models import Plan from plans.models import Plan
from django.conf import settings from django.conf import settings
@ -8,10 +8,11 @@ from django_q.tasks import async_task
from django.core.mail import send_mail from django.core.mail import send_mail
from appointment.models import StaffMember from appointment.models import StaffMember
from django.utils.translation import activate from django.utils.translation import activate
from django.core.files.base import ContentFile
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from allauth.account.models import EmailAddress from allauth.account.models import EmailAddress
from django.core.mail import EmailMultiAlternatives
from inventory.models import DealerSettings, Dealer from inventory.models import DealerSettings, Dealer
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.models import User, Group, Permission

View File

@ -517,6 +517,11 @@ urlpatterns = [
views.UserDeleteview, views.UserDeleteview,
name="user_delete", name="user_delete",
), ),
path(
"<slug:dealer_slug>/user/<int:user_pk>/password_reset/",
views.staff_password_reset_view,
name="staff_password_reset",
),
path( path(
"<slug:dealer_slug>/group/create/", "<slug:dealer_slug>/group/create/",
views.GroupCreateView.as_view(), views.GroupCreateView.as_view(),

View File

@ -1,4 +1,6 @@
# Standard # Standard
from django.core.files.base import ContentFile
import base64
import os import os
import re import re
import io import io
@ -15,7 +17,7 @@ from random import randint
from decimal import Decimal from decimal import Decimal
from io import TextIOWrapper from io import TextIOWrapper
from django.apps import apps from django.apps import apps
from datetime import datetime from datetime import datetime, timedelta
from calendar import month_name from calendar import month_name
from pyzbar.pyzbar import decode from pyzbar.pyzbar import decode
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
@ -301,18 +303,20 @@ def dealer_signup(request):
:rtype: Union[django.http.HttpResponse, django.http.JsonResponse] :rtype: Union[django.http.HttpResponse, django.http.JsonResponse]
""" """
if request.method == "POST": if request.method == "POST":
data = json.loads(request.body) try:
data = json.loads(request.body)
email = data.get("email") email = data.get("email")
password = data.get("password") password = data.get("password")
password_confirm = data.get("confirm_password") password_confirm = data.get("confirm_password")
name = data.get("name") name = data.get("name")
arabic_name = data.get("arabic_name") arabic_name = data.get("arabic_name")
phone = data.get("phone_number") phone = data.get("phone_number")
crn = data.get("crn") crn = data.get("crn")
vrn = data.get("vrn") vrn = data.get("vrn")
address = data.get("address") address = data.get("address")
except json.JSONDecodeError:
pass
if User.objects.filter(email=email).exists(): if User.objects.filter(email=email).exists():
return JsonResponse({"error": _("Email already exists")}, status=400) return JsonResponse({"error": _("Email already exists")}, status=400)
if not re.match( if not re.match(
@ -418,11 +422,11 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
entity = dealer.entity entity = dealer.entity
total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter( qs = models.Car.objects.filter(dealer=dealer)
reserved_until__gte=timezone.now() total_cars = qs.count()
).count()
stats = models.CarFinance.objects.aggregate( stats = models.CarFinance.objects.filter(car__dealer=dealer).aggregate(
total_cost_price=Sum("cost_price"), total_cost_price=Sum("cost_price"),
total_selling_price=Sum("selling_price"), total_selling_price=Sum("selling_price"),
) )
@ -439,72 +443,48 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
canceled_leads = models.Lead.objects.filter( canceled_leads = models.Lead.objects.filter(
dealer=dealer, status=models.Status.UNQUALIFIED dealer=dealer, status=models.Status.UNQUALIFIED
).count() ).count()
available_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.AVAILABLE
).count()
sold_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.SOLD
).count()
reserved_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.RESERVED
).count()
hold_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.HOLD
).count()
damaged_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.DAMAGED
).count()
transfer_cars = models.Car.objects.filter(
dealer=dealer, status=models.CarStatusChoices.TRANSFER
).count()
try:
reserved_percentage = reserved_cars / total_cars * 100
except ZeroDivisionError as e:
print(f"error: {e}")
try:
sold_percentage = sold_cars / total_cars * 100
except ZeroDivisionError as e:
print(f"error: {e}")
qs = ( car_status_qs = qs.values("status").annotate(count=Count("status"))
models.Car.objects.values("id_car_make__name") car_status = {status: count for status, count in car_status_qs}
.annotate(count=Count("id"))
.order_by("id_car_make__name") car_by_make_qs = qs.values("id_car_make__name").annotate(count=Count("id"))
) car_by_make = list(car_by_make_qs)
car_by_make = list(qs)
total_activity = models.UserActivityLog.objects.filter(user=dealer.user).count()
staff = models.Staff.objects.filter(dealer=dealer).count()
total_leads = models.Lead.objects.filter(dealer=dealer).count()
invoices = entity.get_invoices().count()
customers = entity.get_customers().count()
purchase_orders = entity.get_purchase_orders().count()
estimates = entity.get_estimates().count()
context["dealer"] = dealer context["dealer"] = dealer
context["total_activity"] = total_activity context["total_activity"] = models.UserActivityLog.objects.filter(
user=dealer.user
).count()
context["total_cars"] = total_cars context["total_cars"] = total_cars
context["total_reservations"] = total_reservations context["total_reservations"] = models.CarReservation.objects.filter(
reserved_until__gte=timezone.now()
).count()
context["total_cost_price"] = total_cost_price context["total_cost_price"] = total_cost_price
context["total_selling_price"] = total_selling_price context["total_selling_price"] = total_selling_price
context["total_profit"] = total_profit context["total_profit"] = total_profit
context["new_leads"] = new_leads context["new_leads"] = new_leads
context["pending_leads"] = pending_leads context["pending_leads"] = pending_leads
context["canceled_leads"] = canceled_leads context["canceled_leads"] = canceled_leads
context["reserved_percentage"] = reserved_percentage
context["sold_percentage"] = sold_percentage
context["available_cars"] = available_cars
context["sold_cars"] = sold_cars
context["reserved_cars"] = reserved_cars
context["hold_cars"] = hold_cars
context["damaged_cars"] = damaged_cars
context["transfer_cars"] = transfer_cars
context["car"] = json.dumps(car_by_make) context["car"] = json.dumps(car_by_make)
context["customers"] = customers context["available_cars"] = car_status.get(
context["staff"] = staff models.CarStatusChoices.AVAILABLE, 0
context["total_leads"] = total_leads )
context["invoices"] = invoices context["sold_cars"] = car_status.get(models.CarStatusChoices.SOLD, 0)
context["estimates"] = estimates context["reserved_cars"] = car_status.get(
context["purchase_orders"] = purchase_orders models.CarStatusChoices.RESERVED, 0
)
context["hold_cars"] = car_status.get(models.CarStatusChoices.HOLD, 0)
context["damaged_cars"] = car_status.get(
models.CarStatusChoices.DAMAGED, 0
)
context["transfer_cars"] = car_status.get(
models.CarStatusChoices.TRANSFER, 0
)
context["customers"] = entity.get_customers().count()
context["staff"] = models.Staff.objects.filter(dealer=dealer).count()
context["total_leads"] = models.Lead.objects.filter(dealer=dealer).count()
context["invoices"] = entity.get_invoices().count()
context["estimates"] = entity.get_estimates().count()
context["purchase_orders"] = entity.get_purchase_orders().count()
return context return context
@ -3416,6 +3396,7 @@ class UserCreateView(
success_url = reverse_lazy("user_list") success_url = reverse_lazy("user_list")
success_message = _("User created successfully") success_message = _("User created successfully")
permission_required = ["inventory.add_staff"] permission_required = ["inventory.add_staff"]
staff_pk = None
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
@ -3425,13 +3406,6 @@ class UserCreateView(
def form_valid(self, form): def form_valid(self, form):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
# quota_dict = get_user_quota(dealer.user)
# allowed_users = quota_dict.get("Users")
# if allowed_users is None:
# messages.error(self.request, _("The user quota for staff members is not defined. Please contact support"))
# return self.form_invalid(form)
if dealer.is_staff_exceed_quota_limit: if dealer.is_staff_exceed_quota_limit:
messages.error( messages.error(
self.request, self.request,
@ -3463,12 +3437,14 @@ class UserCreateView(
staff.staff_member = staff_member staff.staff_member = staff_member
staff.dealer = dealer staff.dealer = dealer
staff.save() staff.save()
self.staff_pk = staff.pk
for customgroup in form.cleaned_data["group"]: for customgroup in form.cleaned_data["group"]:
staff.add_group(customgroup.group) staff.add_group(customgroup.group)
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse_lazy("user_list", args=[self.request.dealer.slug]) return reverse_lazy("staff_password_reset", args=[self.request.dealer.slug, self.staff_pk])
# return reverse_lazy("user_list", args=[self.request.dealer.slug])
class UserUpdateView( class UserUpdateView(
@ -6148,6 +6124,7 @@ def lead_tracking(request, dealer_slug):
@permission_required("inventory.change_lead", raise_exception=True) @permission_required("inventory.change_lead", raise_exception=True)
def update_lead_actions(request, dealer_slug): def update_lead_actions(request, dealer_slug):
# get the user info for logging # get the user info for logging
user_username = ( user_username = (
request.user.username if request.user.is_authenticated else "anonymous" request.user.username if request.user.is_authenticated else "anonymous"
) )
@ -9391,9 +9368,7 @@ def payment_callback(request, dealer_slug):
history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first() history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first()
payment_status = request.GET.get("status") payment_status = request.GET.get("status")
order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW
if payment_status == "paid": if payment_status == "paid":
# Get or create billing info (optional step)
billing_info, created = BillingInfo.objects.get_or_create( billing_info, created = BillingInfo.objects.get_or_create(
user=dealer.user, user=dealer.user,
defaults={ defaults={
@ -9407,14 +9382,20 @@ def payment_callback(request, dealer_slug):
) )
try: try:
# COMPLETE THE ORDER - This handles plan activation/upgrade order.complete_order()
order.complete_order() # Critical step: activates the plan user = order.user
pricing = order.get_plan_pricing().pricing
logger.info(f"Processing order completion for {user} - upgrading to {order.plan}")
user.userplan.plan = order.plan
user.userplan.expire = datetime.now() + timedelta(days=pricing.period)
user.userplan.save()
user.save()
logger.info(f"User {user} upgraded to {order.plan} plan successfully")
# Update payment history
history.status = "paid" history.status = "paid"
history.save() history.save()
# Retrieve invoice
invoice = order.get_invoices().first() invoice = order.get_invoices().first()
return render( return render(
request, request,
@ -9423,7 +9404,6 @@ def payment_callback(request, dealer_slug):
) )
except Exception as e: except Exception as e:
# Handle activation errors (log, notify admin, etc.)
logger.error(f"Plan activation failed: {str(e)}") logger.error(f"Plan activation failed: {str(e)}")
history.status = "failed" history.status = "failed"
history.save() history.save()
@ -9434,7 +9414,6 @@ def payment_callback(request, dealer_slug):
history.save() history.save()
return render(request, "payment_failed.html", {"message": message}) return render(request, "payment_failed.html", {"message": message})
# Handle unexpected status
return render(request, "payment_failed.html", {"message": "Unknown payment status"}) return render(request, "payment_failed.html", {"message": "Unknown payment status"})
# def payment_callback(request, dealer_slug): # def payment_callback(request, dealer_slug):
# message = request.GET.get("message") # message = request.GET.get("message")
@ -9685,6 +9664,7 @@ def update_schedule(request, dealer_slug, pk):
@permission_required("inventory.add_notes", raise_exception=True) @permission_required("inventory.add_notes", raise_exception=True)
def add_note(request, dealer_slug, content_type, slug): def add_note(request, dealer_slug, content_type, slug):
# Get user information for logging # Get user information for logging
print("hi")
user_username = ( user_username = (
request.user.username if request.user.is_authenticated else "anonymous" request.user.username if request.user.is_authenticated else "anonymous"
) )
@ -9743,6 +9723,7 @@ def add_note(request, dealer_slug, content_type, slug):
@permission_required("inventory.change_notes", raise_exception=True) @permission_required("inventory.change_notes", raise_exception=True)
def update_note(request, dealer_slug, pk): def update_note(request, dealer_slug, pk):
note = get_object_or_404(models.Notes, pk=pk) note = get_object_or_404(models.Notes, pk=pk)
print(note)
lead = get_object_or_404(models.Lead, pk=note.content_object.id) lead = get_object_or_404(models.Lead, pk=note.content_object.id)
dealer = get_object_or_404(models.Dealer, slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
if request.method == "POST": if request.method == "POST":
@ -10750,3 +10731,21 @@ def car_sale_report_csv_export(request,dealer_slug):
]) ])
return response return response
@login_required
# @permission_required('inventory.view_staff')
def staff_password_reset_view(request, dealer_slug, user_pk):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
staff = models.Staff.objects.filter(dealer=dealer, pk=user_pk).first()
if request.method == 'POST':
form = forms.CustomSetPasswordForm(staff.user, request.POST)
if form.is_valid():
form.save()
messages.success(request, _('Your password has been set. You may go ahead and log in now.'))
return redirect('user_detail',dealer_slug=dealer_slug,slug=staff.slug)
else:
messages.error(request, _('Invalid password. Please try again.'))
form = forms.CustomSetPasswordForm(staff.user)
return render(request, 'users/user_password_reset.html', {'form': form})

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,389 +0,0 @@
{% 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 'Sign Up' %}</h3>
<p class="text-body-tertiary fs-9">{% trans 'Create your 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-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 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>
<section class="pt-lg-0 pt-xl-8">
{% include 'footer.html' %}
</section>
<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('056')) {
return false;
}
return true
}
function validate_sa_phone_number(phone_number) {
const phone_numberRegex = /^056[0-9]{7}$/;
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 'Loading' %}...",
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 'account_signup' %}";
const csrftoken = getCookie('csrftoken');
try {
showLoading();
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': '{{csrf_token}}',
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
hideLoading();
const data = await response.json();
if (response.ok) {
notify("success","Account created successfully");
setTimeout(() => {
window.location.href = "{% url 'account_login' %}";
}, 1000);
} else {
notify("error",data.error);
}
} catch (error) {
notify("error",error);
}
}
</script>
{% endblock customJS %}

View File

@ -282,9 +282,9 @@
</div> </div>
</div> </div>
</section> </section>
<section class="pt-lg-0 pt-xl-8">
{% include 'footer.html' %} {% include 'footer.html' %}
</section>
<script src="{% static 'js/phoenix.js' %}"></script> <script src="{% static 'js/phoenix.js' %}"></script>
{% endblock content %} {% endblock content %}
{% block customJS %} {% block customJS %}
@ -307,7 +307,7 @@
return true return true
} }
function validate_sa_phone_number(phone_number) { function validate_sa_phone_number(phone_number) {
const phone_numberRegex = /^05[0-9]{8}$/; const phone_numberRegex = /^056[0-9]{7}$/;
return phone_numberRegex.test(phone_number) && phone_numberRegex !== ''; return phone_numberRegex.test(phone_number) && phone_numberRegex !== '';
} }
function getAllFormData() { function getAllFormData() {

View File

@ -70,6 +70,7 @@
<script src="{% static 'js/main.js' %}"></script> <script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'js/jquery.min.js' %}"></script> <script src="{% static 'js/jquery.min.js' %}"></script>
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %} {% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
{% comment %} {% block customCSS %}{% endblock %} {% endcomment %} {% comment %} {% block customCSS %}{% endblock %} {% endcomment %}
</head> </head>

View File

@ -15,7 +15,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form action="{% url 'add_note' request.dealer.slug content_type slug %}" <form id="noteForm" action="{% url 'add_note' request.dealer.slug content_type slug %}"
hx-select="#notesTable" hx-select="#notesTable"
hx-target="#notesTable" hx-target="#notesTable"
hx-on::after-request="{ hx-on::after-request="{
@ -34,11 +34,20 @@
</div> </div>
</div> </div>
<script> <script>
function updateNote(e) { function updateNote(e) {
let url = e.getAttribute('data-url') let url = e.getAttribute('data-url');
let note = e.getAttribute('data-note') let note = e.getAttribute('data-note');
document.querySelector('#id_note').value = note
let form = document.querySelector('.add_note_form') document.querySelector('#id_note').value = note;
form.action = url
let form = document.querySelector('#noteForm');
form.action = url;
htmx.process(form);
} }
$('#noteModal').on('hidden.bs.modal', function () {
let form = document.querySelector('#noteForm');
form.action = "{% url 'add_note' request.dealer.slug content_type slug %}";
document.querySelector('#id_note').value = "";
htmx.process(form);
});
</script> </script>

View File

@ -39,7 +39,7 @@
<div class="col-12 col-md-auto"> <div class="col-12 col-md-auto">
<h3 class="mb-0">{{ _("Lead Details") }}</h3> <h3 class="mb-0">{{ _("Lead Details") }}</h3>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -47,7 +47,7 @@
<div class="col-md-5 col-lg-5 col-xl-4"> <div class="col-md-5 col-lg-5 col-xl-4">
<div class="sticky-leads-sidebar"> <div class="sticky-leads-sidebar">
<div class="lead-details" data-breakpoint="md"> <div class="lead-details" data-breakpoint="md">
<div class="card mb-2"> <div class="card mb-2">
<div class="card-body"> <div class="card-body">
<div class="row align-items-center g-3 text-center text-xxl-start"> <div class="row align-items-center g-3 text-center text-xxl-start">
@ -800,9 +800,9 @@
<div class="col-auto d-flex"> <div class="col-auto d-flex">
<a class="nav-link px-3 d-block" <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") }} href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("View in Calendar") }}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -824,7 +824,7 @@
let form = document.querySelector('.add_note_form') let form = document.querySelector('.add_note_form')
form.action = "{% url 'add_note' request.dealer.slug 'lead' lead.slug %}" form.action = "{% url 'add_note' request.dealer.slug 'lead' lead.slug %}"
} }
let Toast = Swal.mixin({ /*let Toast = Swal.mixin({
toast: true, toast: true,
position: "top-end", position: "top-end",
showConfirmButton: false, showConfirmButton: false,
@ -834,7 +834,7 @@
toast.onmouseenter = Swal.stopTimer; toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer; toast.onmouseleave = Swal.resumeTimer;
} }
}); });*/
// Display Django messages // Display Django messages
{% if messages %} {% if messages %}

View File

@ -121,7 +121,7 @@
background-position:left bottom; background-position:left bottom;
background-size:auto"></div> background-size:auto"></div>
<div class="card-body d-flex flex-column justify-content-between position-relative"> <div class="card-body d-flex flex-column justify-content-between position-relative">
<div class="d-flex justify-content-between" hx-boost="false"> <div class="d-flex justify-content-between">
<div class="mb-5 mb-md-0 mb-lg-5"> <div class="mb-5 mb-md-0 mb-lg-5">
<div class="d-sm-flex d-md-block d-lg-flex align-items-center mb-3"> <div class="d-sm-flex d-md-block d-lg-flex align-items-center mb-3">
<h3 class="mb-0 me-2">{{ dealer.user.userplan.plan|capfirst }}</h3> <h3 class="mb-0 me-2">{{ dealer.user.userplan.plan|capfirst }}</h3>

View File

@ -310,162 +310,219 @@
{% endblock content %} {% endblock content %}
{% block customJS %} {% block customJS %}
<script> <script>
document.addEventListener("DOMContentLoaded", function () { // wizard-form.js
const form = document.getElementById('wizardForm'); document.addEventListener('DOMContentLoaded', initWizardForm);
document.addEventListener('htmx:afterSwap', initWizardForm);
form.addEventListener('submit', function(e) { function initWizardForm() {
e.preventDefault(); // Prevent default form submission const form = document.getElementById('wizardForm');
if (!form) return;
// Show loading alert // Remove old event listeners to prevent duplicates
form.removeEventListener('submit', handleFormSubmit);
// Add new submit handler
form.addEventListener('submit', handleFormSubmit);
// Initialize radio button selections
initPlanSelection();
// Initialize wizard steps
initWizardSteps();
// Initialize card input formatting
initCardInputs();
// Initialize summary updates
initSummaryUpdates();
}
function handleFormSubmit(e) {
e.preventDefault();
if (typeof Swal !== 'undefined') {
Swal.fire({ Swal.fire({
title: 'Processing...', title: 'Processing...',
html: 'Please wait while we submit your form', html: 'Please wait while we submit your form',
allowOutsideClick: false, allowOutsideClick: false,
didOpen: () => { didOpen: () => Swal.showLoading()
Swal.showLoading();
}
}); });
}
// Submit the form after a slight delay to ensure Swal is shown // Submit the form after a slight delay
setTimeout(() => { setTimeout(() => {
form.submit(); const form = e.target;
}, 100); if (form.reportValidity()) {
}); form.submit();
} else if (typeof Swal !== 'undefined') {
const radios = document.querySelectorAll('.btn-check'); Swal.close();
radios.forEach(radio => {
radio.addEventListener('change', function () {
document.querySelectorAll('.pricing-card .card').forEach(card => {
card.classList.remove('selected');
});
if (this.checked) {
const selectedCard = document.querySelector(`label[for="${this.id}"] .card`);
selectedCard.classList.add('selected');
}
});
// Trigger change on page load if checked
if (radio.checked) {
const selectedCard = document.querySelector(`label[for="${radio.id}"] .card`);
selectedCard.classList.add('selected');
} }
}); }, 100);
}
////////////////////////////////////////////////////////////////////////////////////// function initPlanSelection() {
////////////////////////////////////////////////////////////////////////////////////// const radios = document.querySelectorAll('.btn-check');
let currentStep = 0; radios.forEach(radio => {
const steps = document.querySelectorAll(".step"); // Remove old listeners
const nextBtn = document.getElementById("nextBtn"); radio.removeEventListener('change', handlePlanChange);
const prevBtn = document.getElementById("prevBtn");
const submitBtn = document.getElementById("submitBtn");
function showStep(index) { // Add new listeners
radio.addEventListener('change', handlePlanChange);
// Initialize selected state
if (radio.checked) {
updatePlanSelection(radio.id);
}
});
}
function handlePlanChange(e) {
if (this.checked) {
updatePlanSelection(this.id);
}
}
function updatePlanSelection(radioId) {
document.querySelectorAll('.pricing-card .card').forEach(card => {
card.classList.remove('selected');
});
const selectedCard = document.querySelector(`label[for="${radioId}"] .card`);
if (selectedCard) {
selectedCard.classList.add('selected');
}
}
function initWizardSteps() {
let currentStep = 0;
const steps = document.querySelectorAll(".step");
const nextBtn = document.getElementById("nextBtn");
const prevBtn = document.getElementById("prevBtn");
const submitBtn = document.getElementById("submitBtn");
if (!steps.length || !nextBtn || !prevBtn || !submitBtn) return;
// Remove old listeners
nextBtn.removeEventListener("click", handleNext);
prevBtn.removeEventListener("click", handlePrev);
// Add new listeners
nextBtn.addEventListener("click", handleNext);
prevBtn.addEventListener("click", handlePrev);
function showStep(index) {
steps.forEach((step, i) => { steps.forEach((step, i) => {
step.classList.toggle("d-none", i !== index); step.classList.toggle("d-none", i !== index);
}); });
prevBtn.disabled = index === 0; prevBtn.disabled = index === 0;
nextBtn.classList.toggle("d-none", index === steps.length - 1); nextBtn.classList.toggle("d-none", index === steps.length - 1);
submitBtn.classList.toggle("d-none", index !== steps.length - 1); submitBtn.classList.toggle("d-none", index !== steps.length - 1);
// If last step, populate summary
if (index === steps.length - 1) { if (index === steps.length - 1) {
populateSummary(); populateSummary();
} }
} }
function populateSummary() { function handleNext() {
const selectedPlan = document.querySelector('input[name="selected_plan"]:checked');
document.getElementById("summary_plan").textContent = selectedPlan.dataset.name;
document.getElementById("summary_price").textContent = selectedPlan.dataset.price;
/*const currencyElement = document.createElement("span");
currencyElement.classList.add("currency");
currencyElement.textContent = "<span class="icon-saudi_riyal"></span>";
document.getElementById("summary_price").appendChild(currencyElement);*/
document.getElementById("summary_name").textContent = document.getElementById("first_name").value + " " + document.getElementById("last_name").value;
document.getElementById("summary_email").textContent = document.getElementById("email").value;
document.getElementById("summary_company").textContent = document.getElementById("company").value;
document.getElementById("summary_phone").textContent = document.getElementById("phone").value;
document.getElementById("summary_card_name").textContent = document.getElementById("card_name").value;
document.getElementById("summary_card_number").textContent = maskCard(document.getElementById("card_number").value);
document.getElementById("summary_card_expiry").textContent = document.getElementById("card_expiry").value;
}
function maskCard(cardNumber) {
const last4 = cardNumber.slice(-4);
return "**** **** **** " + last4;
}
nextBtn.addEventListener("click", () => {
if (currentStep < steps.length - 1) { if (currentStep < steps.length - 1) {
currentStep++; currentStep++;
showStep(currentStep); showStep(currentStep);
} }
}); }
prevBtn.addEventListener("click", () => { function handlePrev() {
if (currentStep > 0) { if (currentStep > 0) {
currentStep--; currentStep--;
showStep(currentStep); showStep(currentStep);
} }
}); }
// Highlight selected plan function populateSummary() {
document.querySelectorAll(".btn-check").forEach(input => {
input.addEventListener("change", () => {
document.querySelectorAll(".pricing-card .card").forEach(card => card.classList.remove("selected"));
document.querySelector(`label[for="${input.id}"] .card`).classList.add("selected");
});
if (input.checked) {
document.querySelector(`label[for="${input.id}"] .card`).classList.add("selected");
}
});
showStep(currentStep);
//////////////////////////////////////////////////////////////////////////////////////
const cardNumberInput = document.getElementById("card_number");
cardNumberInput.addEventListener("input", function (e) {
let val = cardNumberInput.value.replace(/\D/g, "").substring(0, 16); // Only digits
let formatted = val.replace(/(.{4})/g, "$1 ").trim();
cardNumberInput.value = formatted;
});
// Format expiry date as MM/YY
const expiryInput = document.getElementById("card_expiry");
expiryInput.addEventListener("input", function (e) {
let val = expiryInput.value.replace(/\D/g, "").substring(0, 4); // Only digits
if (val.length >= 3) {
val = val.substring(0, 2) + "/" + val.substring(2);
}
expiryInput.value = val;
});
//////////////////////////////////////////////////////////////////////////////////////
const planInputs = document.querySelectorAll("input[name='selected_plan']");
const summaryPlanName = document.getElementById("summary_plan");
const summaryPrice = document.getElementById("summary_price"); //summary_price
const summaryTax = document.getElementById("summary-tax");
const summaryTotal = document.getElementById("summary-total");
function updateSummary() {
const selectedPlan = document.querySelector('input[name="selected_plan"]:checked'); const selectedPlan = document.querySelector('input[name="selected_plan"]:checked');
if (selectedPlan) { if (selectedPlan) {
const price = parseFloat(selectedPlan.dataset.price); document.getElementById("summary_plan").textContent = selectedPlan.dataset.name;
const tax = price * 0.15; document.getElementById("summary_price").textContent = selectedPlan.dataset.price;
const total = price + tax;
document.getElementById("summary_price").textContent = price.toFixed(2);
document.getElementById("summary-tax").textContent = tax.toFixed(2);
document.getElementById("summary-total").textContent = total.toFixed(2);
} }
}
const firstName = document.getElementById("first_name")?.value || '';
const lastName = document.getElementById("last_name")?.value || '';
document.getElementById("summary_name").textContent = `${firstName} ${lastName}`.trim();
planInputs.forEach(input => input.addEventListener("change", updateSummary)); document.getElementById("summary_email").textContent = document.getElementById("email")?.value || '';
updateSummary(); // Initial call document.getElementById("summary_company").textContent = document.getElementById("company")?.value || '';
document.getElementById("summary_phone").textContent = document.getElementById("phone")?.value || '';
document.getElementById("summary_card_name").textContent = document.getElementById("card_name")?.value || '';
const cardNumber = document.getElementById("card_number")?.value || '';
document.getElementById("summary_card_number").textContent = maskCard(cardNumber);
document.getElementById("summary_card_expiry").textContent = document.getElementById("card_expiry")?.value || '';
}
function maskCard(cardNumber) {
const last4 = cardNumber.slice(-4);
return "**** **** **** " + last4;
}
// Initialize
showStep(currentStep);
}
function initCardInputs() {
const cardNumberInput = document.getElementById("card_number");
const expiryInput = document.getElementById("card_expiry");
if (cardNumberInput) {
cardNumberInput.removeEventListener("input", formatCardNumber);
cardNumberInput.addEventListener("input", formatCardNumber);
}
if (expiryInput) {
expiryInput.removeEventListener("input", formatExpiryDate);
expiryInput.addEventListener("input", formatExpiryDate);
}
function formatCardNumber(e) {
let val = this.value.replace(/\D/g, "").substring(0, 16);
this.value = val.replace(/(.{4})/g, "$1 ").trim();
}
function formatExpiryDate(e) {
let val = this.value.replace(/\D/g, "").substring(0, 4);
if (val.length >= 3) {
val = val.substring(0, 2) + "/" + val.substring(2);
}
this.value = val;
}
}
function initSummaryUpdates() {
const planInputs = document.querySelectorAll("input[name='selected_plan']");
planInputs.forEach(input => {
input.removeEventListener("change", updatePricingSummary);
input.addEventListener("change", updatePricingSummary);
}); });
updatePricingSummary(); // Initial call
}
function updatePricingSummary() {
const selectedPlan = document.querySelector('input[name="selected_plan"]:checked');
if (!selectedPlan) return;
const price = parseFloat(selectedPlan.dataset.price) || 0;
const tax = price * 0.15;
const total = price + tax;
const summaryPrice = document.getElementById("summary_price");
const summaryTax = document.getElementById("summary-tax");
const summaryTotal = document.getElementById("summary-total");
if (summaryPrice) summaryPrice.textContent = price.toFixed(2);
if (summaryTax) summaryTax.textContent = tax.toFixed(2);
if (summaryTotal) summaryTotal.textContent = total.toFixed(2);
}
</script> </script>
{% endblock customJS %} {% endblock customJS %}

View File

@ -36,6 +36,9 @@
<p> <p>
<strong>{{ _("Arabic Name") }}:</strong> {{ user_.arabic_name }} <strong>{{ _("Arabic Name") }}:</strong> {{ user_.arabic_name }}
</p> </p>
<p>
<strong>{{ _("Email") }}:</strong> {{ user_.email }}
</p>
</div> </div>
<div class="col-md-5"> <div class="col-md-5">
<p> <p>
@ -94,6 +97,11 @@
{{ _("Back to List") }} {{ _("Back to List") }}
<i class="fa-regular fa-circle-left"></i> <i class="fa-regular fa-circle-left"></i>
</a> </a>
<a class="btn btn-sm btn-phoenix-secondary"
href="{% url 'staff_password_reset' request.dealer.slug user_.pk %}">
{{ _("Reset Password") }}
<i class="fa-solid fa-key"></i>
</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -6,7 +6,6 @@
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
{%if users %} {%if users %}
<section class=""> <section class="">
<div class="row mt-4"> <div class="row mt-4">
@ -23,7 +22,7 @@
role="alert"> role="alert">
<i class="fa-solid fa-circle-info fs-6"></i> <i class="fa-solid fa-circle-info fs-6"></i>
<p class="mb-0 flex-1"> <p class="mb-0 flex-1">
{{ _("No Active Subscription,please activate your subscription.") }}<a hx-boost="false" href="{% url 'pricing_page' request.dealer.slug %}" {{ _("No Active Subscription,please activate your subscription.") }}<a href="{% url 'pricing_page' request.dealer.slug %}"
class="ms-3 text-body-primary fs-9">Manage Subscription</a> class="ms-3 text-body-primary fs-9">Manage Subscription</a>
</p> </p>
<button class="btn-close" <button class="btn-close"
@ -91,11 +90,11 @@
</section> </section>
{% else %} {% else %}
{% if request.user.userplan %} {% if request.user.userplan %}
{% url "user_create" request.dealer.slug as create_staff_url %} {% url "user_create" request.dealer.slug as create_staff_url %}
{% include "empty-illustration-page.html" with value="staff" url=create_staff_url %} {% include "empty-illustration-page.html" with value="staff" url=create_staff_url %}
{% else %} {% else %}
<div class="alert alert-outline-warning d-flex align-items-center" <div class="alert alert-outline-warning d-flex align-items-center"
role="alert"> role="alert">
@ -113,6 +112,6 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,22 @@
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card mt-5">
<div class="card-body">
<h2 class="card-title">Set New Password</h2>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">
Change Password
</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}