Free and open source ticket system written in python
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

📦 NEW: Add dark mode support and registration functionality

+529 -47
+42 -1
core/forms.py
··· 12 12 attrs={'class': 'select select-bordered w-full'})) 13 13 telegram_username = forms.CharField(required=False, widget=forms.TextInput(attrs={ 14 14 'class': 'grow', 'placeholder': 'Telegram Username'})) 15 + use_darkmode = forms.BooleanField( 16 + required=False, widget=forms.CheckboxInput(attrs={'class': 'toggle toggle-secondary'})) 15 17 16 18 class Meta: 17 19 model = PawUser 18 20 fields = ('email', 'profile_picture', 19 - 'language', 'telegram_username') 21 + 'language', 'telegram_username', 'use_darkmode') 22 + 23 + 24 + class RegisterForm(forms.Form): 25 + username = forms.CharField(required=True, widget=forms.TextInput( 26 + attrs={'class': 'input input-bordered w-full'})) 27 + email = forms.EmailField(required=True, widget=forms.EmailInput( 28 + attrs={'class': 'input input-bordered w-full'})) 29 + password = forms.CharField(required=True, widget=forms.PasswordInput( 30 + attrs={'class': 'input input-bordered w-full'})) 31 + password_confirm = forms.CharField(required=True, widget=forms.PasswordInput( 32 + attrs={'class': 'input input-bordered w-full'})) 33 + 34 + class Meta: 35 + model = PawUser 36 + fields = ('username', 'email', 'password') 37 + 38 + def clean(self): 39 + cleaned_data = super(RegisterForm, self).clean() 40 + password = cleaned_data.get("password") 41 + password_confirm = cleaned_data.get("password_confirm") 42 + 43 + if password != password_confirm: 44 + raise forms.ValidationError( 45 + "password and password_confirm does not match" 46 + ) 47 + if len(password) < 10: 48 + raise forms.ValidationError( 49 + "password must be at least 10 characters long" 50 + ) 51 + 52 + if PawUser.objects.filter(username=cleaned_data.get("username")).exists(): 53 + raise forms.ValidationError( 54 + "An account with this username already exists" 55 + ) 56 + if PawUser.objects.filter(email=cleaned_data.get("email")).exists(): 57 + raise forms.ValidationError( 58 + "An account with this email already exists" 59 + ) 60 + return cleaned_data
+18
core/migrations/0002_pawuser_use_darkmode.py
··· 1 + # Generated by Django 5.0.3 on 2024-03-10 00:38 2 + 3 + from django.db import migrations, models 4 + 5 + 6 + class Migration(migrations.Migration): 7 + 8 + dependencies = [ 9 + ("core", "0001_initial"), 10 + ] 11 + 12 + operations = [ 13 + migrations.AddField( 14 + model_name="pawuser", 15 + name="use_darkmode", 16 + field=models.BooleanField(default=False), 17 + ), 18 + ]
+1
core/models.py
··· 7 7 upload_to='profile_pics/', null=True, blank=True) 8 8 language = models.CharField(max_length=2, default='en') 9 9 telegram_username = models.CharField(max_length=50, null=True, blank=True) 10 + use_darkmode = models.BooleanField(default=False) 10 11 11 12 def __str__(self): 12 13 return self.username
+2 -1
core/urls.py
··· 1 1 from django.urls import path 2 2 3 - from .views import home_view, logout_view, settings_view 3 + from .views import home_view, logout_view, settings_view, register_view 4 4 5 5 urlpatterns = [ 6 6 path("", home_view, name="home"), 7 7 path("settings", settings_view, name="settings"), 8 + path("register", register_view, name="register"), 8 9 path("logout", logout_view, name="logout"), 9 10 ]
+22 -2
core/views.py
··· 1 1 from django.shortcuts import render, redirect 2 2 from django.contrib.auth import logout 3 - from .forms import UserChangeForm 3 + from .forms import UserChangeForm, RegisterForm 4 + from .models import PawUser 4 5 from django.utils import translation 5 6 from django.conf import settings 6 7 from django.contrib.auth.decorators import login_required ··· 16 17 return res 17 18 18 19 20 + def register_view(request): 21 + 22 + if request.method == "POST": 23 + form = RegisterForm(request.POST) 24 + if form.is_valid(): 25 + PawUser.objects.create_user( 26 + username=form.cleaned_data.get("username"), 27 + email=form.cleaned_data.get("email"), 28 + password=form.cleaned_data.get("password") 29 + ) 30 + return redirect("login") 31 + else: 32 + form = RegisterForm() 33 + 34 + return render(request, "core/register.html", {"form": form}) 35 + 36 + 19 37 def logout_view(request): 20 38 logout(request) 21 39 return redirect("login") ··· 35 53 request.user.email = form.cleaned_data["email"] 36 54 request.user.language = form.cleaned_data["language"] 37 55 request.user.telegram_username = form.cleaned_data["telegram_username"] 56 + request.user.use_darkmode = form.cleaned_data["use_darkmode"] 38 57 if form.cleaned_data["profile_picture"]: 39 58 request.user.profile_picture = form.cleaned_data["profile_picture"] 40 59 request.user.save() ··· 42 61 form = UserChangeForm(initial={ 43 62 "email": request.user.email, 44 63 "language": request.user.language, 45 - "telegram_username": request.user.telegram_username 64 + "telegram_username": request.user.telegram_username, 65 + "use_darkmode": request.user.use_darkmode, 46 66 }) 47 67 48 68 res = render(request, "core/settings.html", {"form": form})
+1 -1
paw/__init__.py
··· 1 1 from django import get_version 2 2 3 - VERSION = (0, 1, 0, "beta", 8) 3 + VERSION = (0, 1, 0, "beta", 9) 4 4 5 5 __version__ = get_version(VERSION)
+1 -1
paw/templates/base.html
··· 1 1 <!DOCTYPE html> 2 2 {% load static %} 3 - <html lang="en"> 3 + <html lang="en"{% if request.user and request.user.use_darkmode %} data-theme="dark" {% endif %}> 4 4 <head> 5 5 <meta charset="UTF-8"> 6 6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
+53
paw/templates/core/register.html
··· 1 + <!-- templates/registration/register.html --> 2 + {% extends 'base.html' %} 3 + {% block content %} 4 + {% load i18n %} 5 + <div class="flex flex-col w-full max-w-xl mx-auto h-full min-h-screen justify-center"> 6 + <div class="p-4"> 7 + <div class="mb-4"> 8 + {% include 'partials/logo.html' with responsive=False %} 9 + </div> 10 + <h1 class="text-3xl font-bold p-2">Register a new account</h1> 11 + <div class="bg-base-300 rounded p-8"> 12 + <form method="post"> 13 + {% csrf_token %} 14 + 15 + {% if form.non_field_errors %} 16 + <div role="alert" class="alert alert-error mb-4"> 17 + <svg xmlns="http://www.w3.org/2000/svg" class="hidden sm:block stroke-current shrink-0 h-6 w-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 10l4 4m0 -4l-4 4" /><path d="M12 3c7.2 0 9 1.8 9 9s-1.8 9 -9 9s-9 -1.8 -9 -9s1.8 -9 9 -9z" /></svg> 18 + <span>{{ form.non_field_errors }}</span> 19 + </div> 20 + {% endif %} 21 + 22 + <div> 23 + <label class="label"> 24 + <span class="text-base label-text" for="{{ form.username.id_for_label }}">Username</span> 25 + </label> 26 + {{ form.username }} 27 + </div> 28 + <div> 29 + <label class="label"> 30 + <span class="text-base label-text" for="{{ form.email.id_for_label }}">Email Address</span> 31 + </label> 32 + {{ form.email}} 33 + </div> 34 + <div> 35 + <label class="label"> 36 + <span class="text-base label-text" for="{{ form.password.id_for_label }}">Password</span> 37 + </label> 38 + {{ form.password }} 39 + </div> 40 + <div> 41 + <label class="label"> 42 + <span class="text-base label-text" for="{{ form.password_confirm.id_for_label }}">Confirm Password</span> 43 + </label> 44 + {{ form.password_confirm }} 45 + </div> 46 + <div class="flex justify-end mt-4"> 47 + <button type="submit" class="btn btn-accent">Register</button> 48 + </div> 49 + </form> 50 + </div> 51 + </div> 52 + </div> 53 + {% endblock %}
+7
paw/templates/core/settings.html
··· 20 20 {{ form.language }} 21 21 </label> 22 22 23 + <div class="form-control w-full mb-2"> 24 + <label class="label cursor-pointer"> 25 + <span class="label-text font-semibold text-base-content" for="{{ form.use_darkmode.id_for_label }}">{% trans 'Use Darkmode' %}</span> 26 + {{ form.use_darkmode }} 27 + </label> 28 + </div> 29 + 23 30 <label class="form-control w-full mb-2"> 24 31 <div class="label"> 25 32 <span for="{{ form.profile_picture.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Profile Picture' %}</span>
+3 -5
paw/templates/dashboard_base.html
··· 4 4 <div class="flex h-full min-h-screen"> 5 5 <div class="flex-none w-20 lg:w-72 fixed h-full bg-neutral text-neutral-content p-4 overflow-y-auto"> 6 6 <div class="flex flex-col h-full"> 7 - <div class="text-accent flex flex-row justify-center items-center mt-4 mb-10"> 8 - <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current h-12 w-12"viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 10c-1.32 0 -1.983 .421 -2.931 1.924l-.244 .398l-.395 .688a50.89 50.89 0 0 0 -.141 .254c-.24 .434 -.571 .753 -1.139 1.142l-.55 .365c-.94 .627 -1.432 1.118 -1.707 1.955c-.124 .338 -.196 .853 -.193 1.28c0 1.687 1.198 2.994 2.8 2.994l.242 -.006c.119 -.006 .234 -.017 .354 -.034l.248 -.043l.132 -.028l.291 -.073l.162 -.045l.57 -.17l.763 -.243l.455 -.136c.53 -.15 .94 -.222 1.283 -.222c.344 0 .753 .073 1.283 .222l.455 .136l.764 .242l.569 .171l.312 .084c.097 .024 .187 .045 .273 .062l.248 .043c.12 .017 .235 .028 .354 .034l.242 .006c1.602 0 2.8 -1.307 2.8 -3c0 -.427 -.073 -.939 -.207 -1.306c-.236 -.724 -.677 -1.223 -1.48 -1.83l-.257 -.19l-.528 -.38c-.642 -.47 -1.003 -.826 -1.253 -1.278l-.27 -.485l-.252 -.432c-1.011 -1.696 -1.618 -2.099 -3.053 -2.099z" stroke-width="0" fill="currentColor" /><path d="M19.78 7h-.03c-1.219 .02 -2.35 1.066 -2.908 2.504c-.69 1.775 -.348 3.72 1.075 4.333c.256 .109 .527 .163 .801 .163c1.231 0 2.38 -1.053 2.943 -2.504c.686 -1.774 .34 -3.72 -1.076 -4.332a2.05 2.05 0 0 0 -.804 -.164z" stroke-width="0" fill="currentColor" /><path d="M9.025 3c-.112 0 -.185 .002 -.27 .015l-.093 .016c-1.532 .206 -2.397 1.989 -2.108 3.855c.272 1.725 1.462 3.114 2.92 3.114l.187 -.005a1.26 1.26 0 0 0 .084 -.01l.092 -.016c1.533 -.206 2.397 -1.989 2.108 -3.855c-.27 -1.727 -1.46 -3.114 -2.92 -3.114z" stroke-width="0" fill="currentColor" /><path d="M14.972 3c-1.459 0 -2.647 1.388 -2.916 3.113c-.29 1.867 .574 3.65 2.174 3.867c.103 .013 .2 .02 .296 .02c1.39 0 2.543 -1.265 2.877 -2.883l.041 -.23c.29 -1.867 -.574 -3.65 -2.174 -3.867a2.154 2.154 0 0 0 -.298 -.02z" stroke-width="0" fill="currentColor" /><path d="M4.217 7c-.274 0 -.544 .054 -.797 .161c-1.426 .615 -1.767 2.562 -1.078 4.335c.563 1.451 1.71 2.504 2.941 2.504c.274 0 .544 -.054 .797 -.161c1.426 -.615 1.767 -2.562 1.078 -4.335c-.563 -1.451 -1.71 -2.504 -2.941 -2.504z" stroke-width="0" fill="currentColor" /></svg> 9 - <span class="hidden lg:block text-4xl font-mono font-bold">paw</span> 10 - <span class="hidden lg:block badge badge-sm self-end">{{ app_version }}</span> 11 - </div> 7 + <a href="" class="mt-4 mb-10"> 8 + {% include 'partials/logo.html' with responsive=True %} 9 + </a> 12 10 <a href="{% url 'create_ticket' %}" class="btn btn-ghost btn-square lg:w-full lg:p-4 lg:btn-lg lg:justify-start"> 13 11 <svg xmlns="http://www.w3.org/2000/svg" class="w-7 h-7" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" /><path d="M13.5 6.5l4 4" /></svg> 14 12 <span class="hidden lg:block">{% trans 'Create Ticket' %}</span>
+8
paw/templates/partials/logo.html
··· 1 + {% block logo %} 2 + {% load i18n %} 3 + <div class="text-accent flex flex-row justify-center items-center"> 4 + <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current h-12 w-12"viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 10c-1.32 0 -1.983 .421 -2.931 1.924l-.244 .398l-.395 .688a50.89 50.89 0 0 0 -.141 .254c-.24 .434 -.571 .753 -1.139 1.142l-.55 .365c-.94 .627 -1.432 1.118 -1.707 1.955c-.124 .338 -.196 .853 -.193 1.28c0 1.687 1.198 2.994 2.8 2.994l.242 -.006c.119 -.006 .234 -.017 .354 -.034l.248 -.043l.132 -.028l.291 -.073l.162 -.045l.57 -.17l.763 -.243l.455 -.136c.53 -.15 .94 -.222 1.283 -.222c.344 0 .753 .073 1.283 .222l.455 .136l.764 .242l.569 .171l.312 .084c.097 .024 .187 .045 .273 .062l.248 .043c.12 .017 .235 .028 .354 .034l.242 .006c1.602 0 2.8 -1.307 2.8 -3c0 -.427 -.073 -.939 -.207 -1.306c-.236 -.724 -.677 -1.223 -1.48 -1.83l-.257 -.19l-.528 -.38c-.642 -.47 -1.003 -.826 -1.253 -1.278l-.27 -.485l-.252 -.432c-1.011 -1.696 -1.618 -2.099 -3.053 -2.099z" stroke-width="0" fill="currentColor" /><path d="M19.78 7h-.03c-1.219 .02 -2.35 1.066 -2.908 2.504c-.69 1.775 -.348 3.72 1.075 4.333c.256 .109 .527 .163 .801 .163c1.231 0 2.38 -1.053 2.943 -2.504c.686 -1.774 .34 -3.72 -1.076 -4.332a2.05 2.05 0 0 0 -.804 -.164z" stroke-width="0" fill="currentColor" /><path d="M9.025 3c-.112 0 -.185 .002 -.27 .015l-.093 .016c-1.532 .206 -2.397 1.989 -2.108 3.855c.272 1.725 1.462 3.114 2.92 3.114l.187 -.005a1.26 1.26 0 0 0 .084 -.01l.092 -.016c1.533 -.206 2.397 -1.989 2.108 -3.855c-.27 -1.727 -1.46 -3.114 -2.92 -3.114z" stroke-width="0" fill="currentColor" /><path d="M14.972 3c-1.459 0 -2.647 1.388 -2.916 3.113c-.29 1.867 .574 3.65 2.174 3.867c.103 .013 .2 .02 .296 .02c1.39 0 2.543 -1.265 2.877 -2.883l.041 -.23c.29 -1.867 -.574 -3.65 -2.174 -3.867a2.154 2.154 0 0 0 -.298 -.02z" stroke-width="0" fill="currentColor" /><path d="M4.217 7c-.274 0 -.544 .054 -.797 .161c-1.426 .615 -1.767 2.562 -1.078 4.335c.563 1.451 1.71 2.504 2.941 2.504c.274 0 .544 -.054 .797 -.161c1.426 -.615 1.767 -2.562 1.078 -4.335c-.563 -1.451 -1.71 -2.504 -2.941 -2.504z" stroke-width="0" fill="currentColor" /></svg> 5 + <span class="{% if responsive %} hidden lg:block {% endif %} text-4xl font-mono font-bold">paw</span> 6 + <span class="{% if responsive %} hidden lg:block {% endif %} badge badge-sm self-end">{{ app_version }}</span> 7 + </div> 8 + {% endblock %}
+7 -1
paw/templates/placeholder.html
··· 7 7 <option>None</option> 8 8 </select> 9 9 <input type="file" class="file-input file-input-bordered w-full max-w-xs" /> 10 - <input type="text" class="grow" placeholder="Username" /> 10 + <input type="text" class="grow" placeholder="Username" /> 11 + <div class="form-control"> 12 + <label class="label cursor-pointer"> 13 + <span class="label-text">Remember me</span> 14 + <input type="checkbox" class="toggle toggle-secondary" checked /> 15 + </label> 16 + </div>
+6 -3
paw/templates/registration/login.html
··· 3 3 {% block content %} 4 4 {% load i18n %} 5 5 <div class="flex flex-col w-full max-w-xl mx-auto h-full min-h-screen justify-center"> 6 - <div class=""> 6 + <div class="p-4"> 7 + <div class="mb-4"> 8 + {% include 'partials/logo.html' with responsive=False %} 9 + </div> 7 10 <h1 class="text-3xl font-bold p-2">Log In</h1> 8 11 <div class="bg-base-300 rounded p-8"> 9 12 <form method="post"> ··· 12 15 <label class="label"> 13 16 <span class="text-base label-text" for="{{ form.username.id_for_label }}">Username</span> 14 17 </label> 15 - <input type="text" name="username" placeholder="Username" class="w-full input input-bordered input-primary" /> 18 + <input type="text" name="username" placeholder="Username" class="w-full input input-bordered" /> 16 19 </div> 17 20 <div> 18 21 <label class="label"> 19 22 <span class="text-base label-text" for="{{ form.password.id_for_label }}">Password</span> 20 23 </label> 21 24 <input type="password" name="password" placeholder="Enter Password" 22 - class="w-full input input-bordered input-primary" /> 25 + class="w-full input input-bordered" /> 23 26 </div> 24 27 <div class="flex justify-end mt-4"> 25 28 <button type="submit" class="btn btn-accent">Log In</button>
+8 -8
paw/templates/ticketing/ticket_detail.html
··· 84 84 85 85 <div class="order-first lg:order-none w-full lg:max-w-md border-l-2 border-base-300 bg-base-200 p-8"> 86 86 <h1 class="text-xl font-bold">Ticket #{{ ticket.id }}</h1> 87 - <div class="text-neutral text-xs mb-4">{% trans 'Created by' %} {{ ticket.user.username }}</div> 87 + <div class="text-base-content/85 text-xs mb-4">{% trans 'Created by' %} {{ ticket.user.username }}</div> 88 88 <div class="mb-4"> 89 89 <div class="join"> 90 90 <span class="badge badge-neutral join-item">Status</span> ··· 95 95 {% include 'partials/ticket_priority_badge.html' with ticket=ticket %} 96 96 </div> 97 97 </div> 98 - <div class="my-4 text-neutral flex items-center text-sm font-semibold"> 98 + <div class="my-4 text-base-content/85 flex items-center text-sm font-semibold"> 99 99 <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 5m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M16 3l0 4" /><path d="M8 3l0 4" /><path d="M4 11l16 0" /><path d="M8 15h2v2h-2z" /></svg> 100 100 {% trans 'Created on' %} {{ ticket.created_at }} 101 101 </div> 102 - <div class="text-neutral flex items-center text-sm font-semibold"> 102 + <div class="text-base-content/85 flex items-center text-sm font-semibold"> 103 103 <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 5m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M16 3l0 4" /><path d="M8 3l0 4" /><path d="M4 11l16 0" /><path d="M8 15h2v2h-2z" /></svg> 104 104 {% trans 'Last updated' %} {{ ticket.updated_at }} 105 105 </div> 106 106 107 107 <h2 class="font-semibold text-xs mt-4 mb-2">{% trans 'Contact' %}</h2> 108 - <div class="text-neutral flex items-center text-sm mb-1"> 108 + <div class="text-base-content/85 flex items-center text-sm mb-1"> 109 109 <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z" /><path d="M3 7l9 6l9 -6" /></svg> 110 110 <a href="mailto:{{ ticket.user.email }}" class="ml-2 underline">{{ ticket.user.email }}</a> 111 111 </div> 112 112 {% if ticket.user.telegram_username %} 113 - <div class="text-neutral flex items-center text-sm mb-1"> 113 + <div class="text-base-content/85 flex items-center text-sm mb-1"> 114 114 <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 10l-4 4l6 6l4 -16l-18 7l4 2l2 6l3 -4" /></svg> 115 115 <span class="ml-2">{{ ticket.user.telegram_username }}</span> 116 116 </div> ··· 118 118 119 119 <div class="divider"></div> 120 120 <h2 class="font-semibold mb-4">{% trans 'Category' %}</h2> 121 - <div class="text-neutral flex items-center text-sm mb-6"> 121 + <div class="text-base-content/85 flex items-center text-sm mb-6"> 122 122 <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4h6v6h-6z" /><path d="M14 4h6v6h-6z" /><path d="M4 14h6v6h-6z" /><path d="M17 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /></svg> 123 123 {% if ticket.category %} 124 124 {{ ticket.category.name }} ··· 138 138 {% endif %} 139 139 <div class="divider"></div> 140 140 <h2 class="font-semibold mb-4">{% trans 'Assignees' %}</h2> 141 - <div class="text-neutral flex items-center text-sm mb-4"> 141 + <div class="text-base-content/85 flex items-center text-sm mb-4"> 142 142 <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg> 143 143 {% include 'partials/assigned_to.html' with assigned_to=ticket.assigned_to %} 144 144 {% if request.user.is_staff and ticket.assigned_to != request.user %} ··· 148 148 </form> 149 149 {% endif %} 150 150 </div> 151 - <div class="text-neutral flex items-center text-sm mb-6"> 151 + <div class="text-base-content/85 flex items-center text-sm mb-6"> 152 152 <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" /><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M17 10h2a2 2 0 0 1 2 2v1" /><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M3 13v-1a2 2 0 0 1 2 -2h2" /></svg> 153 153 {% if ticket.assigned_team %} 154 154 {{ ticket.assigned_team.name }}
+331 -23
static/css/paw.css
··· 539 539 --tab-radius: 0.2rem; 540 540 } 541 541 542 + @media (prefers-color-scheme: dark) { 543 + :root { 544 + color-scheme: dark; 545 + --in: 0.7206 0.191 231.6; 546 + --pc: 0.13138 0.0392 275.75; 547 + --sc: 0.139303 0.011822 248.687186; 548 + --ac: 0.154929 0.01245 217.469017; 549 + --inc: 0 0 0; 550 + --suc: 0.153654 0.01498 131.063061; 551 + --wac: 0.170972 0.017847 84.093335; 552 + --erc: 0.12122 0.024119 15.341883; 553 + --animation-btn: 0.25s; 554 + --animation-input: .2s; 555 + --btn-focus-scale: 0.95; 556 + --border-btn: 1px; 557 + --tab-border: 1px; 558 + --p: 0.6569 0.196 275.75; 559 + --s: 0.696516 0.059108 248.687186; 560 + --a: 0.774643 0.062249 217.469017; 561 + --n: 0.313815 0.021108 254.139175; 562 + --nc: 0.746477 0.0216 264.435964; 563 + --b1: 0.253267 0.015896 252.417568; 564 + --b2: 0.232607 0.013807 253.100675; 565 + --b3: 0.211484 0.01165 254.087939; 566 + --bc: 0.746477 0.0216 264.435964; 567 + --su: 0.76827 0.074899 131.063061; 568 + --wa: 0.854862 0.089234 84.093335; 569 + --er: 0.6061 0.120594 15.341883; 570 + --rounded-box: 0.4rem; 571 + --rounded-btn: 0.2rem; 572 + --rounded-badge: 0.4rem; 573 + --tab-radius: 0.2rem; 574 + } 575 + } 576 + 577 + [data-theme=light] { 578 + color-scheme: light; 579 + --pc: 0.118872 0.015449 254.027774; 580 + --sc: 0.139303 0.011822 248.687186; 581 + --ac: 0.154929 0.01245 217.469017; 582 + --inc: 0.138414 0.012499 332.664922; 583 + --suc: 0.153654 0.01498 131.063061; 584 + --wac: 0.170972 0.017847 84.093335; 585 + --erc: 0.12122 0.024119 15.341883; 586 + --animation-btn: 0.25s; 587 + --animation-input: .2s; 588 + --btn-focus-scale: 0.95; 589 + --border-btn: 1px; 590 + --tab-border: 1px; 591 + --p: 0.594359 0.077246 254.027774; 592 + --s: 0.696516 0.059108 248.687186; 593 + --a: 0.774643 0.062249 217.469017; 594 + --n: 0.45229 0.035214 264.1312; 595 + --nc: 0.899258 0.016374 262.749256; 596 + --b1: 0.951276 0.007445 260.731539; 597 + --b2: 0.932996 0.010389 261.788485; 598 + --b3: 0.899258 0.016374 262.749256; 599 + --bc: 0.324374 0.022945 264.182036; 600 + --in: 0.692072 0.062496 332.664922; 601 + --su: 0.76827 0.074899 131.063061; 602 + --wa: 0.854862 0.089234 84.093335; 603 + --er: 0.6061 0.120594 15.341883; 604 + --rounded-box: 0.4rem; 605 + --rounded-btn: 0.2rem; 606 + --rounded-badge: 0.4rem; 607 + --tab-radius: 0.2rem; 608 + } 609 + 610 + [data-theme=dark] { 611 + color-scheme: dark; 612 + --in: 0.7206 0.191 231.6; 613 + --pc: 0.13138 0.0392 275.75; 614 + --sc: 0.139303 0.011822 248.687186; 615 + --ac: 0.154929 0.01245 217.469017; 616 + --inc: 0 0 0; 617 + --suc: 0.153654 0.01498 131.063061; 618 + --wac: 0.170972 0.017847 84.093335; 619 + --erc: 0.12122 0.024119 15.341883; 620 + --animation-btn: 0.25s; 621 + --animation-input: .2s; 622 + --btn-focus-scale: 0.95; 623 + --border-btn: 1px; 624 + --tab-border: 1px; 625 + --p: 0.6569 0.196 275.75; 626 + --s: 0.696516 0.059108 248.687186; 627 + --a: 0.774643 0.062249 217.469017; 628 + --n: 0.313815 0.021108 254.139175; 629 + --nc: 0.746477 0.0216 264.435964; 630 + --b1: 0.253267 0.015896 252.417568; 631 + --b2: 0.232607 0.013807 253.100675; 632 + --b3: 0.211484 0.01165 254.087939; 633 + --bc: 0.746477 0.0216 264.435964; 634 + --su: 0.76827 0.074899 131.063061; 635 + --wa: 0.854862 0.089234 84.093335; 636 + --er: 0.6061 0.120594 15.341883; 637 + --rounded-box: 0.4rem; 638 + --rounded-btn: 0.2rem; 639 + --rounded-badge: 0.4rem; 640 + --tab-radius: 0.2rem; 641 + } 642 + 542 643 *, ::before, ::after { 543 644 --tw-border-spacing-x: 0; 544 645 --tw-border-spacing-y: 0; ··· 637 738 --tw-backdrop-opacity: ; 638 739 --tw-backdrop-saturate: ; 639 740 --tw-backdrop-sepia: ; 741 + } 742 + 743 + .alert { 744 + display: grid; 745 + width: 100%; 746 + grid-auto-flow: row; 747 + align-content: flex-start; 748 + align-items: center; 749 + justify-items: center; 750 + gap: 1rem; 751 + text-align: center; 752 + border-radius: var(--rounded-box, 1rem); 753 + border-width: 1px; 754 + --tw-border-opacity: 1; 755 + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); 756 + padding: 1rem; 757 + --tw-text-opacity: 1; 758 + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); 759 + --alert-bg: var(--fallback-b2,oklch(var(--b2)/1)); 760 + --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1)); 761 + background-color: var(--alert-bg); 762 + } 763 + 764 + @media (min-width: 640px) { 765 + .alert { 766 + grid-auto-flow: column; 767 + grid-template-columns: auto minmax(auto,1fr); 768 + justify-items: start; 769 + text-align: start; 770 + } 640 771 } 641 772 642 773 .avatar { ··· 839 970 line-height: 1.25rem; 840 971 } 841 972 842 - .chat-footer { 843 - grid-row-start: 3; 844 - font-size: 0.875rem; 845 - line-height: 1.25rem; 846 - } 847 - 848 973 .chat-bubble { 849 974 position: relative; 850 975 display: block; ··· 943 1068 [dir="rtl"] .chat-end .chat-bubble:before { 944 1069 -webkit-mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 945 1070 mask-image: url("data:image/svg+xml,%3csvg width='3' height='3' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m 0 3 L 3 3 L 3 0 C 3 1 1 3 0 3'/%3e%3c/svg%3e"); 1071 + } 1072 + 1073 + .checkbox { 1074 + flex-shrink: 0; 1075 + --chkbg: var(--fallback-bc,oklch(var(--bc)/1)); 1076 + --chkfg: var(--fallback-b1,oklch(var(--b1)/1)); 1077 + height: 1.5rem; 1078 + width: 1.5rem; 1079 + cursor: pointer; 1080 + -webkit-appearance: none; 1081 + -moz-appearance: none; 1082 + appearance: none; 1083 + border-radius: var(--rounded-btn, 0.5rem); 1084 + border-width: 1px; 1085 + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); 1086 + --tw-border-opacity: 0.2; 946 1087 } 947 1088 948 1089 .divider { ··· 1174 1315 margin-inline-end: -1rem; 1175 1316 } 1176 1317 1318 + .input-lg[type="number"]::-webkit-inner-spin-button { 1319 + margin-top: -1.5rem; 1320 + margin-bottom: -1.5rem; 1321 + margin-inline-end: -1.5rem; 1322 + } 1323 + 1177 1324 .join { 1178 1325 display: inline-flex; 1179 1326 align-items: stretch; ··· 1380 1527 background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); 1381 1528 } 1382 1529 1530 + .toggle { 1531 + flex-shrink: 0; 1532 + --tglbg: var(--fallback-b1,oklch(var(--b1)/1)); 1533 + --handleoffset: 1.5rem; 1534 + --handleoffsetcalculator: calc(var(--handleoffset) * -1); 1535 + --togglehandleborder: 0 0; 1536 + height: 1.5rem; 1537 + width: 3rem; 1538 + cursor: pointer; 1539 + -webkit-appearance: none; 1540 + -moz-appearance: none; 1541 + appearance: none; 1542 + border-radius: var(--rounded-badge, 1.9rem); 1543 + border-width: 1px; 1544 + border-color: currentColor; 1545 + background-color: currentColor; 1546 + color: var(--fallback-bc,oklch(var(--bc)/0.5)); 1547 + transition: background, 1548 + box-shadow var(--animation-input, 0.2s) ease-out; 1549 + box-shadow: var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset, 1550 + 0 0 0 2px var(--tglbg) inset, 1551 + var(--togglehandleborder); 1552 + } 1553 + 1554 + .alert-error { 1555 + border-color: var(--fallback-er,oklch(var(--er)/0.2)); 1556 + --tw-text-opacity: 1; 1557 + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); 1558 + --alert-bg: var(--fallback-er,oklch(var(--er)/1)); 1559 + --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1)); 1560 + } 1561 + 1383 1562 .avatar-group :where(.avatar) { 1384 1563 overflow: hidden; 1385 1564 border-radius: 9999px; ··· 1703 1882 border-radius: inherit; 1704 1883 } 1705 1884 1885 + .checkbox:focus { 1886 + box-shadow: none; 1887 + } 1888 + 1889 + .checkbox:focus-visible { 1890 + outline-style: solid; 1891 + outline-width: 2px; 1892 + outline-offset: 2px; 1893 + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); 1894 + } 1895 + 1896 + .checkbox:checked, 1897 + .checkbox[checked="true"], 1898 + .checkbox[aria-checked="true"] { 1899 + background-repeat: no-repeat; 1900 + animation: checkmark var(--animation-input, 0.2s) ease-out; 1901 + background-color: var(--chkbg); 1902 + background-image: linear-gradient(-45deg, transparent 65%, var(--chkbg) 65.99%), 1903 + linear-gradient(45deg, transparent 75%, var(--chkbg) 75.99%), 1904 + linear-gradient(-45deg, var(--chkbg) 40%, transparent 40.99%), 1905 + linear-gradient( 1906 + 45deg, 1907 + var(--chkbg) 30%, 1908 + var(--chkfg) 30.99%, 1909 + var(--chkfg) 40%, 1910 + transparent 40.99% 1911 + ), 1912 + linear-gradient(-45deg, var(--chkfg) 50%, var(--chkbg) 50.99%); 1913 + } 1914 + 1915 + .checkbox:indeterminate { 1916 + --tw-bg-opacity: 1; 1917 + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); 1918 + background-repeat: no-repeat; 1919 + animation: checkmark var(--animation-input, 0.2s) ease-out; 1920 + background-image: linear-gradient(90deg, transparent 80%, var(--chkbg) 80%), 1921 + linear-gradient(-90deg, transparent 80%, var(--chkbg) 80%), 1922 + linear-gradient(0deg, var(--chkbg) 43%, var(--chkfg) 43%, var(--chkfg) 57%, var(--chkbg) 57%); 1923 + } 1924 + 1925 + .checkbox:disabled { 1926 + cursor: not-allowed; 1927 + border-color: transparent; 1928 + --tw-bg-opacity: 1; 1929 + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); 1930 + opacity: 0.2; 1931 + } 1932 + 1706 1933 @keyframes checkmark { 1707 1934 0% { 1708 1935 background-position-y: 5px; ··· 1795 2022 outline-width: 2px; 1796 2023 outline-offset: 2px; 1797 2024 outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); 1798 - } 1799 - 1800 - .input-primary { 1801 - --tw-border-opacity: 1; 1802 - border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); 1803 - } 1804 - 1805 - .input-primary:focus, 1806 - .input-primary:focus-within { 1807 - --tw-border-opacity: 1; 1808 - border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); 1809 - outline-color: var(--fallback-p,oklch(var(--p)/1)); 1810 2025 } 1811 2026 1812 2027 .input-disabled, ··· 2099 2314 } 2100 2315 } 2101 2316 2317 + [dir="rtl"] .toggle { 2318 + --handleoffsetcalculator: calc(var(--handleoffset) * 1); 2319 + } 2320 + 2321 + .toggle:focus-visible { 2322 + outline-style: solid; 2323 + outline-width: 2px; 2324 + outline-offset: 2px; 2325 + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); 2326 + } 2327 + 2328 + .toggle:hover { 2329 + background-color: currentColor; 2330 + } 2331 + 2332 + .toggle:checked, 2333 + .toggle[checked="true"], 2334 + .toggle[aria-checked="true"] { 2335 + background-image: none; 2336 + --handleoffsetcalculator: var(--handleoffset); 2337 + --tw-text-opacity: 1; 2338 + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); 2339 + } 2340 + 2341 + [dir="rtl"] .toggle:checked, [dir="rtl"] .toggle[checked="true"], [dir="rtl"] .toggle[aria-checked="true"] { 2342 + --handleoffsetcalculator: calc(var(--handleoffset) * -1); 2343 + } 2344 + 2345 + .toggle:indeterminate { 2346 + --tw-text-opacity: 1; 2347 + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); 2348 + box-shadow: calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset, 2349 + calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset, 2350 + 0 0 0 2px var(--tglbg) inset; 2351 + } 2352 + 2353 + [dir="rtl"] .toggle:indeterminate { 2354 + box-shadow: calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset, 2355 + calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset, 2356 + 0 0 0 2px var(--tglbg) inset; 2357 + } 2358 + 2359 + .toggle-secondary:focus-visible { 2360 + outline-color: var(--fallback-s,oklch(var(--s)/1)); 2361 + } 2362 + 2363 + .toggle-secondary:checked, 2364 + .toggle-secondary[checked="true"], 2365 + .toggle-secondary[aria-checked="true"] { 2366 + border-color: var(--fallback-s,oklch(var(--s)/var(--tw-border-opacity))); 2367 + --tw-border-opacity: 0.1; 2368 + --tw-bg-opacity: 1; 2369 + background-color: var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity))); 2370 + --tw-text-opacity: 1; 2371 + color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity))); 2372 + } 2373 + 2374 + .toggle:disabled { 2375 + cursor: not-allowed; 2376 + --tw-border-opacity: 1; 2377 + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); 2378 + background-color: transparent; 2379 + opacity: 0.3; 2380 + --togglehandleborder: 0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset, 2381 + var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset; 2382 + } 2383 + 2102 2384 .badge-sm { 2103 2385 height: 1rem; 2104 2386 font-size: 0.75rem; ··· 2159 2441 width: 2rem; 2160 2442 border-radius: 9999px; 2161 2443 padding: 0px; 2444 + } 2445 + 2446 + .input-lg { 2447 + height: 4rem; 2448 + padding-left: 1.5rem; 2449 + padding-right: 1.5rem; 2450 + font-size: 1.125rem; 2451 + line-height: 1.75rem; 2452 + line-height: 2; 2162 2453 } 2163 2454 2164 2455 .join.join-vertical { ··· 2447 2738 flex: none; 2448 2739 } 2449 2740 2741 + .shrink-0 { 2742 + flex-shrink: 0; 2743 + } 2744 + 2450 2745 .flex-grow { 2451 2746 flex-grow: 1; 2452 2747 } ··· 2629 2924 color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); 2630 2925 } 2631 2926 2632 - .text-error { 2633 - --tw-text-opacity: 1; 2634 - color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity))); 2927 + .text-base-content\/85 { 2928 + color: var(--fallback-bc,oklch(var(--bc)/0.85)); 2635 2929 } 2636 2930 2637 - .text-neutral { 2931 + .text-error { 2638 2932 --tw-text-opacity: 1; 2639 - color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity))); 2933 + color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity))); 2640 2934 } 2641 2935 2642 2936 .text-neutral-content { ··· 2708 3002 border-radius: 9999px; 2709 3003 padding: 0px; 2710 3004 } 3005 + } 2711 3006 3007 + @media (min-width: 640px) { 3008 + .sm\:block { 3009 + display: block; 3010 + } 3011 + } 3012 + 3013 + @media (min-width: 768px) { 3014 + .md\:block { 3015 + display: block; 3016 + } 3017 + } 3018 + 3019 + @media (min-width: 1024px) { 2712 3020 .lg\:order-none { 2713 3021 order: 0; 2714 3022 }
+19 -1
theme/tailwind.config.js
··· 6 6 }, 7 7 plugins: [require("daisyui")], 8 8 daisyui: { 9 - themes: ["nord"], 9 + themes: [ 10 + { 11 + light: { 12 + ...require("daisyui/src/theming/themes")["nord"], 13 + }, 14 + dark: { 15 + ...require("daisyui/src/theming/themes")["dark"], 16 + "accent": "#88C0D0", 17 + "secondary": "#81A1C1", 18 + "success": "#A3BE8C", 19 + "warning": "#EBCB8B", 20 + "error": "#BF616A", 21 + "--rounded-box": "0.4rem", 22 + "--rounded-btn": "0.2rem", 23 + "--rounded-badge": "0.4rem", 24 + "--tab-radius": "0.2rem", 25 + }, 26 + }, 27 + ], 10 28 }, 11 29 } 12 30