New changes.

main
nataly 11 months ago
parent 0970ecba52
commit 4c7d45ea1f

@ -7,8 +7,7 @@ urlpatterns = [
path('service', views.add_service, name='addservice'),
path('order/<int:customer_id>/', views.add_order, name='addorder'),
path('invoice-pdf/<int:order_id>/', views.add_invoice_pdf, name='addinvoice'),
path('payment-pdf/<int:order_id>/', views.add_payment_pdf, name='addpayment'),
path('service/<int:service_id>/<int:order_id>/', views.add_service_in_order, name='addserviceinorder'),
@ -16,5 +15,4 @@ urlpatterns = [
path('payment/<int:order_id>/', views.add_payment_modal, name='add_payment_modal'),
path('payment_comment/', views.add_payment_comment_modal, name='add_payment_comment_modal'),
]

@ -8,7 +8,7 @@ import os
from osinacore.decorators import *
from django.core.files.base import ContentFile
from django.db.models import Q
from weasyprint import HTML, CSS
@staff_login_required
@ -211,116 +211,7 @@ def add_payment_comment_modal(request):
def add_invoice_pdf(request, order_id):
order = get_object_or_404(Order, id=order_id)
current_year = str(timezone.now().year)[-2:]
last_invoice = Invoice.objects.all().last()
if last_invoice:
last_invoice_number = int(last_invoice.invoice_number.split('-')[1].split('+')[0])
new_invoice_number = f"$0{current_year}-{last_invoice_number + 1}"
else:
new_invoice_number = f"$0{current_year}-1425"
invoice = Invoice.objects.create(
invoice_number = new_invoice_number,
order=order,
date_created=datetime.now(),
)
template = get_template('details_templates/invoice-details.html')
context = {'order': order}
html_string = template.render(context)
# Define the CSS string with Poppins font
css_string = '''
@font-face {
font-family: 'Poppins';
src: url('path_to_poppins_font_file.ttf') format('truetype'); /* Update the path to the font file */
}
body {
font-family: 'Poppins', sans-serif; /* Use Poppins font for the entire document */
}
/* Your existing CSS styles */
/* Add or modify styles as needed */
'''
# Generate PDF
pdf = HTML(string=html_string).write_pdf(
stylesheets=[
CSS(string=css_string),
CSS(string='@page { margin: 30px; }')
],
presentational_hints=True
)
filename = f'invoice_{invoice.invoice_number}.pdf'
pdf_content = ContentFile(pdf)
invoice.pdf.save(filename, pdf_content, save=True)
# Return PDF
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="my_pdf.pdf"'
return response
def add_payment_pdf(request, order_id):
order = get_object_or_404(Order, id=order_id)
payments = OrderPayment.objects.filter(order = order)
paid_amount = OrderPayment.objects.filter(order=order, date_paid__isnull=False).aggregate(total_paid=Sum('amount'))['total_paid'] or 0
cart_total = order.get_cart_total
remaining_amount = cart_total - paid_amount
invoice = order.invoice
# Render both invoice and payment details templates to HTML
invoice_template = get_template('details_templates/invoice-details.html')
payment_template = get_template('details_templates/payment-details.html')
invoice_html = invoice_template.render({'order': order})
payment_html = payment_template.render({'order': order, 'payments':payments, 'remaining_amount':remaining_amount,})
# Combine the HTML content of both templates
combined_html = f"{invoice_html}<div style='page-break-before: always;'></div>{payment_html}"
# Define CSS
css_string = '''
@font-face {
font-family: 'Poppins';
src: url('path_to_poppins_font_file.ttf') format('truetype'); /* Update the path to the font file */
}
body {
font-family: 'Poppins', sans-serif; /* Use Poppins font for the entire document */
}
/* Your existing CSS styles */
/* Add or modify styles as needed */
'''
# Generate PDF
pdf = HTML(string=combined_html).write_pdf(
stylesheets=[
CSS(string=css_string),
CSS(string='@page { margin: 30px; }')
],
presentational_hints=True
)
# Return PDF
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="my_pdf.pdf"'
return response

@ -51,7 +51,8 @@
{{invoice.order.orderpayment_set.all.last.date_due}}
{% else %}
-
{% endif %}</p>
{% endif %}
</p>
</td>
<td class="px-6 py-4 text-center text-sm border-r border-gray-300">

@ -0,0 +1,91 @@
{% extends "customer_main.html" %}
{%load static%}
{% block content %}
<div class="w-full px-5 s:px-9 grid grid-cols-1 xxlg1:grid-cols-3 justify-between gap-5">
<div class="w-full flex flex-col gap-5">
<div class="w-full p-9 flex flex-col justify-center items-center gap-5 shadow-md bg-white rounded-md">
<div class="w-[80px] s:w-[100px] h-[80px] s:h-[100px] rounded-full border border-gray-50 shadow-md">
<img src="{% static 'images/ositcom_logos/full-logo-blue-bg.png' %}" class="w-full h-full object-cover rounded-full">
</div>
<div class="text-gray-500 font-light flex flex-col justify-center items-center">
<p>{{request.user.first_name}} {{request.user.last_name}}</p>
<p>{{request.user.email}}</p>
</div>
</div>
<div class="w-full p-5 flex flex-col shadow-md bg-white rounded-md">
<a href="{% url 'userprofile' %}">
<div
class="w-full py-3 border-b border-gray-100 flex justify-start items-center gap-2 text-secondosiblue cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"
class="text-secondosiblue" color="#000000" fill="none">
<path
d="M6.57757 15.4816C5.1628 16.324 1.45336 18.0441 3.71266 20.1966C4.81631 21.248 6.04549 22 7.59087 22H16.4091C17.9545 22 19.1837 21.248 20.2873 20.1966C22.5466 18.0441 18.8372 16.324 17.4224 15.4816C14.1048 13.5061 9.89519 13.5061 6.57757 15.4816Z"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M16.5 6.5C16.5 8.98528 14.4853 11 12 11C9.51472 11 7.5 8.98528 7.5 6.5C7.5 4.01472 9.51472 2 12 2C14.4853 2 16.5 4.01472 16.5 6.5Z"
stroke="currentColor" stroke-width="1.5" />
</svg>
<p>Profile Details</p>
</div>
</a>
<a class="group" href="{% url 'usersettings' %}">
<div class="w-full py-3 flex justify-start items-center gap-2 text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="text-gray-500 group-hover:text-secondosiblue duration-300" width="24" height="24"
color="#000000" fill="none">
<path
d="M15.5 12C15.5 13.933 13.933 15.5 12 15.5C10.067 15.5 8.5 13.933 8.5 12C8.5 10.067 10.067 8.5 12 8.5C13.933 8.5 15.5 10.067 15.5 12Z"
stroke="currentColor" stroke-width="1.5" />
<path
d="M21.011 14.0965C21.5329 13.9558 21.7939 13.8854 21.8969 13.7508C22 13.6163 22 13.3998 22 12.9669V11.0332C22 10.6003 22 10.3838 21.8969 10.2493C21.7938 10.1147 21.5329 10.0443 21.011 9.90358C19.0606 9.37759 17.8399 7.33851 18.3433 5.40087C18.4817 4.86799 18.5509 4.60156 18.4848 4.44529C18.4187 4.28902 18.2291 4.18134 17.8497 3.96596L16.125 2.98673C15.7528 2.77539 15.5667 2.66972 15.3997 2.69222C15.2326 2.71472 15.0442 2.90273 14.6672 3.27873C13.208 4.73448 10.7936 4.73442 9.33434 3.27864C8.95743 2.90263 8.76898 2.71463 8.60193 2.69212C8.43489 2.66962 8.24877 2.77529 7.87653 2.98663L6.15184 3.96587C5.77253 4.18123 5.58287 4.28891 5.51678 4.44515C5.45068 4.6014 5.51987 4.86787 5.65825 5.4008C6.16137 7.3385 4.93972 9.37763 2.98902 9.9036C2.46712 10.0443 2.20617 10.1147 2.10308 10.2492C2 10.3838 2 10.6003 2 11.0332V12.9669C2 13.3998 2 13.6163 2.10308 13.7508C2.20615 13.8854 2.46711 13.9558 2.98902 14.0965C4.9394 14.6225 6.16008 16.6616 5.65672 18.5992C5.51829 19.1321 5.44907 19.3985 5.51516 19.5548C5.58126 19.7111 5.77092 19.8188 6.15025 20.0341L7.87495 21.0134C8.24721 21.2247 8.43334 21.3304 8.6004 21.3079C8.76746 21.2854 8.95588 21.0973 9.33271 20.7213C10.7927 19.2644 13.2088 19.2643 14.6689 20.7212C15.0457 21.0973 15.2341 21.2853 15.4012 21.3078C15.5682 21.3303 15.7544 21.2246 16.1266 21.0133L17.8513 20.034C18.2307 19.8187 18.4204 19.711 18.4864 19.5547C18.5525 19.3984 18.4833 19.132 18.3448 18.5991C17.8412 16.6616 19.0609 14.6226 21.011 14.0965Z"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<p class="group-hover:text-secondosiblue duration-300">Settings</p>
</div>
</a>
</div>
</div>
<div class="w-full col-span-1 xxlg1:col-span-2 flex flex-col gap-5">
<div
class="grid grid-cols-1 s:grid-cols-2 gap-5 h-fit border shadow-md p-5 bg-white rounded-md">
<div class="flex flex-col gap-2">
<label class="text-gray-500 font-light">First Name:</label>
<input type="text"
class="w-full border border-gray-200 px-3 py-3 bg-transparent text-gray-500 font-light rounded-md" disabled
value="{{request.user.first_name}}">
</div>
<div class="flex flex-col gap-2">
<label class="text-gray-500 font-light">Last Name:</label>
<input type="text"
class="w-full border border-gray-200 px-3 py-3 bg-transparent text-gray-500 font-light rounded-md" disabled
value="{{request.user.last_name}}">
</div>
<div class="flex flex-col gap-2">
<label class="text-gray-500 font-light">Email:</label>
<input type="text"
class="w-full border border-gray-200 px-3 py-3 bg-transparent text-gray-500 font-light rounded-md" disabled
value="{{request.user.email}}">
</div>
<div class="flex flex-col gap-2">
<label class="text-gray-500 font-light">Phone Number:</label>
<input type="text"
class="w-full border border-gray-200 px-3 py-3 bg-transparent text-gray-500 font-light rounded-md" disabled
value="{{request.user.customer.mobile_number}}">
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,90 @@
{% extends "customer_main.html" %}
{%load static%}
{% block content %}
<div class="w-full px-5 s:px-9 grid grid-cols-1 xxlg1:grid-cols-3 justify-between gap-5">
<div class="w-full flex flex-col gap-5">
<div class="w-full p-9 flex flex-col justify-center items-center gap-5 shadow-md bg-white rounded-md">
<div class="w-[80px] s:w-[100px] h-[80px] s:h-[100px] rounded-full border border-gray-50 shadow-md">
<img src="{% static 'images/ositcom_logos/full-logo-blue-bg.png' %}" class="w-full h-full object-cover rounded-full">
</div>
<div class="text-gray-500 font-light flex flex-col justify-center items-center">
<p>{{request.user.first_name}} {{request.user.last_name}}</p>
<p>{{request.user.email}}</p>
</div>
</div>
<div class="w-full p-5 flex flex-col shadow-md bg-white rounded-md">
<a class="group" href="{% url 'userprofile' %}">
<div class="w-full py-3 border-b border-gray-100 flex justify-start items-center gap-2 text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"
class="text-gray-500 group-hover:text-secondosiblue duration-300" color="#000000" fill="none">
<path
d="M6.57757 15.4816C5.1628 16.324 1.45336 18.0441 3.71266 20.1966C4.81631 21.248 6.04549 22 7.59087 22H16.4091C17.9545 22 19.1837 21.248 20.2873 20.1966C22.5466 18.0441 18.8372 16.324 17.4224 15.4816C14.1048 13.5061 9.89519 13.5061 6.57757 15.4816Z"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M16.5 6.5C16.5 8.98528 14.4853 11 12 11C9.51472 11 7.5 8.98528 7.5 6.5C7.5 4.01472 9.51472 2 12 2C14.4853 2 16.5 4.01472 16.5 6.5Z"
stroke="currentColor" stroke-width="1.5" />
</svg>
<p class="group-hover:text-secondosiblue duration-300">Profile Details</p>
</div>
</a>
<a class="group" href="{% url 'usersettings' %}">
<div
class="w-full py-3 flex justify-start items-center gap-2 text-secondosiblue cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="" width="24" height="24" fill="none">
<path
d="M15.5 12C15.5 13.933 13.933 15.5 12 15.5C10.067 15.5 8.5 13.933 8.5 12C8.5 10.067 10.067 8.5 12 8.5C13.933 8.5 15.5 10.067 15.5 12Z"
stroke="currentColor" stroke-width="1.5" />
<path
d="M21.011 14.0965C21.5329 13.9558 21.7939 13.8854 21.8969 13.7508C22 13.6163 22 13.3998 22 12.9669V11.0332C22 10.6003 22 10.3838 21.8969 10.2493C21.7938 10.1147 21.5329 10.0443 21.011 9.90358C19.0606 9.37759 17.8399 7.33851 18.3433 5.40087C18.4817 4.86799 18.5509 4.60156 18.4848 4.44529C18.4187 4.28902 18.2291 4.18134 17.8497 3.96596L16.125 2.98673C15.7528 2.77539 15.5667 2.66972 15.3997 2.69222C15.2326 2.71472 15.0442 2.90273 14.6672 3.27873C13.208 4.73448 10.7936 4.73442 9.33434 3.27864C8.95743 2.90263 8.76898 2.71463 8.60193 2.69212C8.43489 2.66962 8.24877 2.77529 7.87653 2.98663L6.15184 3.96587C5.77253 4.18123 5.58287 4.28891 5.51678 4.44515C5.45068 4.6014 5.51987 4.86787 5.65825 5.4008C6.16137 7.3385 4.93972 9.37763 2.98902 9.9036C2.46712 10.0443 2.20617 10.1147 2.10308 10.2492C2 10.3838 2 10.6003 2 11.0332V12.9669C2 13.3998 2 13.6163 2.10308 13.7508C2.20615 13.8854 2.46711 13.9558 2.98902 14.0965C4.9394 14.6225 6.16008 16.6616 5.65672 18.5992C5.51829 19.1321 5.44907 19.3985 5.51516 19.5548C5.58126 19.7111 5.77092 19.8188 6.15025 20.0341L7.87495 21.0134C8.24721 21.2247 8.43334 21.3304 8.6004 21.3079C8.76746 21.2854 8.95588 21.0973 9.33271 20.7213C10.7927 19.2644 13.2088 19.2643 14.6689 20.7212C15.0457 21.0973 15.2341 21.2853 15.4012 21.3078C15.5682 21.3303 15.7544 21.2246 16.1266 21.0133L17.8513 20.034C18.2307 19.8187 18.4204 19.711 18.4864 19.5547C18.5525 19.3984 18.4833 19.132 18.3448 18.5991C17.8412 16.6616 19.0609 14.6226 21.011 14.0965Z"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<p>Settings</p>
</div>
</a>
</div>
</div>
<div class="w-full col-span-1 xxlg1:col-span-2 h-fit shadow-md p-5 rounded-md bg-white">
<p class="text-secondosiblue text-xl font-poppinsBold">Change Password</p>
<form id="settingsContainer" method="POST" action="{% url 'changepassword' %}"
class="w-full flex flex-col gap-3 mt-5">
{% csrf_token %}
<input required name="current_password" type="password"
class="w-full bg-transparent border border-gray-200 px-3 py-3 outline-none rounded-md" id="currentPasswordInput"
placeholder="Current Password">
<p class="text-red-500 font-light" id="oldPasswordMatchError"></p>
<input name="new_password" type="password"
class="w-full bg-transparent border border-gray-200 px-3 py-3 outline-none rounded-md" placeholder="New Password"
required>
<input name="confirm_password" type="password"
class="w-full bg-transparent border border-gray-200 px-3 py-3 outline-none rounded-md"
placeholder="Password Confirmation" required>
<p class="text-red-500 font-light" id="passwordMatchError"></p>
<button type="submit"
class="w-full bg-osiblue border border-osiblue text-white px-5 py-2 uppercase cursor-pointer hover:text-osiblue hover:bg-white duration-300 rounded-md">Submit</button>
</form>
</div>
</div>
<!------------------------------------- JS SCRIPTS --------------------------------------->
<script type="text/javascript" src='{% static "js/customer_dashboard/change-password.js" %}'></script>
{% endblock %}

@ -29,4 +29,12 @@ urlpatterns = [
path('shared-hosting-plans/', views.shared_hosting_plans, name='sharedhostingplans'),
path('cloud-vps-hosting-plans/', views.cloud_vps_hosting_plans, name='cloudvpshostingplans'),
path('dedicated-servers-plans/', views.dedicated_servers_plans, name='dedicatedserversplans'),
# USER PROFILE
path('user-profile/', views.user_profile, name='userprofile'),
path('user-settings/', views.user_settings, name='usersettings'),
path('changepassword/', views.change_password, name="changepassword"),
path('check_current_password/', views.check_current_password, name='check_current_password'),
]

@ -7,6 +7,9 @@ from django.db.models import Q
from django.http import Http404
from django.db.models import OuterRef, Subquery
from customercore.views import *
from django.contrib.auth.hashers import check_password
from django.contrib.auth import update_session_auth_hash, logout
import json
@ -264,3 +267,61 @@ def dedicated_servers_plans(request, *args, **kwargs):
# USER PROFILE
@customer_login_required
def user_profile(request, *args, **kwargs):
context = {
}
return render(request, 'user_profile_pages/user-profile.html', context)
@customer_login_required
def user_settings(request, *args, **kwargs):
context = {
}
return render(request, 'user_profile_pages/user-settings.html', context)
def change_password(request):
if request.method == 'POST':
current_password = request.POST['current_password']
new_password = request.POST['new_password']
confirm_password = request.POST['confirm_password']
if check_password(current_password, request.user.password):
if new_password == confirm_password:
request.user.set_password(new_password)
request.user.save()
update_session_auth_hash(request, request.user)
logout(request)
return redirect('home')
return render(request, 'accounts/change_password.html')
def check_current_password(request):
if request.method == 'POST':
try:
data = json.loads(request.body.decode('utf-8'))
current_password = data.get('current_password')
except json.JSONDecodeError:
return JsonResponse({'is_current_password_correct': False})
user = request.user
if check_password(current_password, request.user.password):
return JsonResponse({'is_current_password_correct': True})
else:
return JsonResponse({'is_current_password_correct': False})
return JsonResponse({'error': 'Invalid request method'}, status=400)

Binary file not shown.

@ -189,13 +189,23 @@
<!-- USER PROFILE DROPDOWN -->
<div class="w-full h-fit bg-secondosiblue px-5 flex flex-col hidden" id="mobileUserProfileDropdown">
<a href="{% url 'signout' %}" class="w-full">
<div class="w-full py-3 flex items-center gap-2 text-white text-[16px]">
<div class="w-full py-3 flex items-center gap-2 text-white text-[16px] border-b border-white border-opacity-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-[25px] text-white">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
</svg>
<p>Logout</p>
</div>
</a>
<a href="{% url 'userprofile' %}" class="">
<div
class="w-full py-3 flex items-center gap-2 text-white cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-[25px] text-white" fill="none">
<path d="M6.57757 15.4816C5.1628 16.324 1.45336 18.0441 3.71266 20.1966C4.81631 21.248 6.04549 22 7.59087 22H16.4091C17.9545 22 19.1837 21.248 20.2873 20.1966C22.5466 18.0441 18.8372 16.324 17.4224 15.4816C14.1048 13.5061 9.89519 13.5061 6.57757 15.4816Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M16.5 6.5C16.5 8.98528 14.4853 11 12 11C9.51472 11 7.5 8.98528 7.5 6.5C7.5 4.01472 9.51472 2 12 2C14.4853 2 16.5 4.01472 16.5 6.5Z" stroke="currentColor" stroke-width="1.5" />
</svg>
<p>User Profile</p>
</div>
</a>
</div>
</div>
@ -384,13 +394,23 @@
style="display: none;" id="userProfileDropdown">
<a href="{% url 'signout' %}" class="w-full">
<div
class="w-full py-3 flex items-center gap-2 text-white hover:text-osiblue duration-300 cursor-pointer">
class="w-full py-3 flex items-center gap-2 text-white hover:text-osiblue duration-300 cursor-pointer border-b border-white border-opacity-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-[25px] text-white">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
</svg>
<p>Logout</p>
</div>
</a>
<a href="{% url 'userprofile' %}" class="">
<div
class="w-full py-3 flex items-center gap-2 text-white hover:text-osiblue duration-300 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-[25px] text-white" fill="none">
<path d="M6.57757 15.4816C5.1628 16.324 1.45336 18.0441 3.71266 20.1966C4.81631 21.248 6.04549 22 7.59087 22H16.4091C17.9545 22 19.1837 21.248 20.2873 20.1966C22.5466 18.0441 18.8372 16.324 17.4224 15.4816C14.1048 13.5061 9.89519 13.5061 6.57757 15.4816Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M16.5 6.5C16.5 8.98528 14.4853 11 12 11C9.51472 11 7.5 8.98528 7.5 6.5C7.5 4.01472 9.51472 2 12 2C14.4853 2 16.5 4.01472 16.5 6.5Z" stroke="currentColor" stroke-width="1.5" />
</svg>
<p>User Profile</p>
</div>
</a>
</div>
</div>
</div>

@ -71,6 +71,13 @@
<p class="text-gray-500 text-xl">Email: <span
class="text-slate-800 text-xl font-semibold">{{customer.user.email}}</span></p>
</div>
<div>
<p class="text-gray-500 text-xl">Mobile Number: <span
class="text-slate-800 text-xl font-semibold">{{customer.mobile_number}}</span></p>
</div>
{% if customer.personal_website %}
<div>
<p class="text-gray-500 text-xl">Personal Website: <span

@ -23,7 +23,7 @@
<body class="bg-gray-200 font-poppinsLight relative">
<!-- THE POPUP THAT APPEARS WHEN THE USER GOES OFFLINE -->
{% if user_offline %}
<div class="w-full h-full inset-0 fixed bg-black bg-opacity-70 flex justify-center items-center p-5 z-20">
<div class="w-full h-full inset-0 fixed bg-black bg-opacity-70 flex justify-center items-center p-5 z-50">
<div class="w-full s:w-[400px] bg-white shadow-md rounded-md flex flex-col justify-between">
<div class="w-full py-9 px-5 flex justify-center items-center text-center">
<p class="text-secondosiblue text-[18px]">You are Currently Offline</p>

@ -74,6 +74,14 @@
{% if project.open_user_tasks_count == 1 %} Task {% else %} Tasks {% endif %}</p>
</div>
<div class="w-full flex justify-start items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-6 h-6 text-secondosiblue" color="#000000" fill="none">
<path d="M2.46433 9.34375C2.21579 9.34375 1.98899 9.14229 2.00041 8.87895C2.06733 7.33687 2.25481 6.33298 2.78008 5.53884C3.08228 5.08196 3.45765 4.68459 3.88923 4.36468C5.05575 3.5 6.70139 3.5 9.99266 3.5H14.0074C17.2986 3.5 18.9443 3.5 20.1108 4.36468C20.5424 4.68459 20.9177 5.08196 21.2199 5.53884C21.7452 6.33289 21.9327 7.33665 21.9996 8.87843C22.011 9.14208 21.7839 9.34375 21.5351 9.34375C20.1493 9.34375 19.0259 10.533 19.0259 12C19.0259 13.467 20.1493 14.6562 21.5351 14.6562C21.7839 14.6562 22.011 14.8579 21.9996 15.1216C21.9327 16.6634 21.7452 17.6671 21.2199 18.4612C20.9177 18.918 20.5424 19.3154 20.1108 19.6353C18.9443 20.5 17.2986 20.5 14.0074 20.5H9.99266C6.70139 20.5 5.05575 20.5 3.88923 19.6353C3.45765 19.3154 3.08228 18.918 2.78008 18.4612C2.25481 17.667 2.06733 16.6631 2.00041 15.1211C1.98899 14.8577 2.21579 14.6562 2.46433 14.6562C3.85012 14.6562 4.97352 13.467 4.97352 12C4.97352 10.533 3.85012 9.34375 2.46433 9.34375Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
<path d="M9 3.5L9 20.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<p class="text-secondosiblue"><span
class="font-poppinsBold">{{project.ticket_set.all.count}}</span> Open {% if project.ticket_set.all.count == 1 %} Ticket {% else %} Tickets {% endif %}</p>
</div>
<div class="w-full flex justify-start items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
@ -88,20 +96,9 @@
{{project.total_time_worked_seconds}}sec</span>
</p>
</div>
</div>
<!-- Action Buttons -->
<div class="w-full border-t border-b l:border-b-0 border-gray-200 grid grid-cols-1 s:grid-cols-3">
<a href="{% url 'detailed-project' project.project_id %}"
class="p-3 border-b s:border-b-0 border-r-0 s:border-r border-gray-200 text-base bg-gray-50 text-secondosiblue flex justify-center items-center">View</a>
<a href="{% url 'editproject' project.project_id %}"
class="p-3 text-base bg-gray-50 border-b s:border-b-0 border-r-0 s:border-r border-gray-200 text-secondosiblue flex justify-center items-center">Edit</a>
<button class="p-3 text-base bg-gray-50 text-secondosiblue editProjectStatusButton"
data-modal-url="{% url 'editprojectstatusmodal' project.id %}">Update Status</button>
</div>
</div>
<!-- Right Section - Recent Notes -->
@ -109,7 +106,7 @@
{% if project.note_set.exists %}
{% with last_note=project.note_set.last %}
<div class="w-full h-full flex flex-col gap-3 justify-center items-center p-3">
<p class="text-secondosiblue break-all whitespace-pre-wrap">{{ last_note.text }}</p>
<p class="text-gray-400 break-all whitespace-pre-wrap text-sm">{{ last_note.text }}</p>
<div class="w-[30px] h-[30px] rounded-full shadow-md p-1 flex justify-center items-center bg-gray-100 cursor-pointer hover:scale-105 duration-300 addProjectNoteButton"
data-modal-url="{% url 'addprojectnotemodal' project.project_id %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"

@ -894,6 +894,14 @@ video {
z-index: 50;
}
.col-span-2 {
grid-column: span 2 / span 2;
}
.col-span-1 {
grid-column: span 1 / span 1;
}
.m-auto {
margin: auto;
}
@ -984,6 +992,11 @@ video {
margin-right: 0.75rem;
}
.ms-0 {
-webkit-margin-start: 0px;
margin-inline-start: 0px;
}
.mt-0 {
margin-top: 0px;
}
@ -1068,11 +1081,6 @@ video {
margin-top: 80px;
}
.ms-0 {
-webkit-margin-start: 0px;
margin-inline-start: 0px;
}
.block {
display: block;
}
@ -1303,10 +1311,6 @@ video {
height: 100vh;
}
.h-2\.5 {
height: 0.625rem;
}
.max-h-\[50px\] {
max-height: 50px;
}
@ -1503,6 +1507,10 @@ video {
width: 80%;
}
.w-\[80px\] {
width: 80px;
}
.w-\[85\%\] {
width: 85%;
}
@ -1524,10 +1532,6 @@ video {
width: 100%;
}
.w-2\.5 {
width: 0.625rem;
}
.min-w-full {
min-width: 100%;
}
@ -1801,6 +1805,10 @@ video {
row-gap: 0.5rem;
}
.gap-y-8 {
row-gap: 2rem;
}
.-space-x-px > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(-1px * var(--tw-space-x-reverse));
@ -1897,6 +1905,11 @@ video {
border-bottom-left-radius: 0.375rem;
}
.rounded-e-lg {
border-start-end-radius: 0.5rem;
border-end-end-radius: 0.5rem;
}
.rounded-e-md {
border-start-end-radius: 0.375rem;
border-end-end-radius: 0.375rem;
@ -1917,6 +1930,11 @@ video {
border-bottom-right-radius: 0.375rem;
}
.rounded-s-lg {
border-start-start-radius: 0.5rem;
border-end-start-radius: 0.5rem;
}
.rounded-t-md {
border-top-left-radius: 0.375rem;
border-top-right-radius: 0.375rem;
@ -1927,16 +1945,6 @@ video {
border-top-right-radius: 0px;
}
.rounded-e-lg {
border-start-end-radius: 0.5rem;
border-end-end-radius: 0.5rem;
}
.rounded-s-lg {
border-start-start-radius: 0.5rem;
border-end-start-radius: 0.5rem;
}
.rounded-bl-lg {
border-bottom-left-radius: 0.5rem;
}
@ -2011,6 +2019,10 @@ video {
border-bottom-width: 2px;
}
.border-e-0 {
border-inline-end-width: 0px;
}
.border-l {
border-left-width: 1px;
}
@ -2027,10 +2039,6 @@ video {
border-top-width: 1px;
}
.border-e-0 {
border-inline-end-width: 0px;
}
.border-none {
border-style: none;
}
@ -2040,6 +2048,11 @@ video {
border-color: rgb(0 0 0 / var(--tw-border-opacity));
}
.border-blue-300 {
--tw-border-opacity: 1;
border-color: rgb(147 197 253 / var(--tw-border-opacity));
}
.border-blue-400 {
--tw-border-opacity: 1;
border-color: rgb(96 165 250 / var(--tw-border-opacity));
@ -2090,6 +2103,11 @@ video {
border-color: rgb(107 114 128 / var(--tw-border-opacity));
}
.border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.border-green-700 {
--tw-border-opacity: 1;
border-color: rgb(21 128 61 / var(--tw-border-opacity));
@ -2175,16 +2193,6 @@ video {
border-color: rgb(202 138 4 / var(--tw-border-opacity));
}
.border-blue-300 {
--tw-border-opacity: 1;
border-color: rgb(147 197 253 / var(--tw-border-opacity));
}
.border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.border-r-transparent {
border-right-color: transparent;
}
@ -2217,6 +2225,11 @@ video {
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
}
.bg-blue-50 {
--tw-bg-opacity: 1;
background-color: rgb(239 246 255 / var(--tw-bg-opacity));
}
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
@ -2446,11 +2459,6 @@ video {
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
}
.bg-blue-50 {
--tw-bg-opacity: 1;
background-color: rgb(239 246 255 / var(--tw-bg-opacity));
}
.bg-opacity-10 {
--tw-bg-opacity: 0.1;
}
@ -3621,11 +3629,6 @@ video {
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.hover\:bg-blue-100:hover {
--tw-bg-opacity: 1;
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
}
.hover\:bg-opacity-100:hover {
--tw-bg-opacity: 1;
}
@ -3638,10 +3641,6 @@ video {
--tw-bg-opacity: 0.6;
}
.hover\:bg-opacity-40:hover {
--tw-bg-opacity: 0.4;
}
.hover\:p-2:hover {
padding: 0.5rem;
}
@ -3701,16 +3700,6 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:text-blue-700:hover {
--tw-text-opacity: 1;
color: rgb(29 78 216 / var(--tw-text-opacity));
}
.hover\:text-gray-700:hover {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.focus\:outline-none:focus {
outline: 2px solid transparent;
outline-offset: 2px;
@ -3726,51 +3715,11 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
:is([dir="rtl"] .rtl\:rotate-180) {
--tw-rotate: 180deg;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@media (prefers-color-scheme: dark) {
.dark\:border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.dark\:bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
}
.dark\:bg-gray-800 {
--tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
}
.dark\:text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.dark\:text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.dark\:hover\:bg-gray-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
}
.dark\:hover\:text-white:hover {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
}
@media (min-width: 650px) {
@ -3879,10 +3828,6 @@ video {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.s\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.s\:flex-row {
flex-direction: row;
}
@ -3895,14 +3840,6 @@ video {
gap: 0px;
}
.s\:border-b-0 {
border-bottom-width: 0px;
}
.s\:border-r {
border-right-width: 1px;
}
.s\:p-9 {
padding: 2.25rem;
}
@ -4065,10 +4002,6 @@ video {
border-top-right-radius: 0px;
}
.l\:border-b-0 {
border-bottom-width: 0px;
}
.l\:border-l {
border-left-width: 1px;
}
@ -4121,6 +4054,10 @@ video {
width: 300px;
}
.xlg1\:w-\[70\%\] {
width: 70%;
}
.xlg1\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@ -4159,6 +4096,10 @@ video {
}
@media (min-width: 1350px) {
.xxlg1\:col-span-2 {
grid-column: span 2 / span 2;
}
.xxlg1\:block {
display: block;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@ -0,0 +1,93 @@
document.addEventListener("DOMContentLoaded", function () {
const currentPasswordInput = document.getElementById("currentPasswordInput");
const oldPasswordMatchError = document.getElementById("oldPasswordMatchError");
const passwordForm = document.getElementById("settingsContainer");
const passwordInput1 = document.querySelector('input[name="new_password"]');
const passwordInput2 = document.querySelector('input[name="confirm_password"]');
const passwordMatchError = document.getElementById('passwordMatchError');
passwordForm.addEventListener("submit", function (e) {
e.preventDefault(); // Prevent the default form submission.
// Clear error messages.
oldPasswordMatchError.textContent = '';
passwordMatchError.textContent = '';
// Get the entered passwords.
const currentPassword = currentPasswordInput.value;
const newPassword1 = passwordInput1.value;
const newPassword2 = passwordInput2.value;
// Function to get the CSRF token from cookies.
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Function to check passwords and perform validation
function validatePasswords() {
// Password strength requirements
const isLengthValid = newPassword1.length >= 8;
if (newPassword1 && newPassword2) {
if (newPassword1 === newPassword2) {
let errorMessage = '';
if (!isLengthValid) {
errorMessage += '- At least 8 characters\n';
}
if (errorMessage) {
passwordMatchError.textContent = errorMessage;
passwordMatchError.style.whiteSpace = 'pre-line';
}
} else {
passwordMatchError.textContent = 'Passwords do not match';
}
}
}
// Check if the passwords match and perform validation
if (currentPassword && newPassword1 && newPassword2) {
// Send an AJAX request to check the current password.
fetch('/check_current_password/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({ current_password: currentPassword }),
})
.then(response => response.json())
.then(data => {
if (data.is_current_password_correct) {
// Current password is correct, now validate the new passwords.
validatePasswords();
// Check if there are any error messages.
if (!oldPasswordMatchError.textContent && !passwordMatchError.textContent) {
// Password checks passed, submit the form.
passwordForm.submit();
}
} else {
// Password is incorrect. Display an error message.
oldPasswordMatchError.textContent = 'Incorrect password';
}
})
.catch(error => {
console.error('Error:', error);
});
}
});
});
Loading…
Cancel
Save