Free and open source ticket system written in python

feat: migrate remaining forms

Signed-off-by: A. Ottr <alex@otter.foo>

+183 -119
+80
paw/static/css/paw.css
··· 620 620 } 621 621 } 622 622 } 623 + .validator-hint { 624 + @layer daisyui.l1.l2.l3 { 625 + visibility: hidden; 626 + margin-top: calc(0.25rem * 2); 627 + font-size: 0.75rem; 628 + } 629 + } 630 + .validator { 631 + @layer daisyui.l1.l2.l3 { 632 + &:user-valid, &:has(:user-valid) { 633 + &, &:focus, &:checked, &[aria-checked="true"], &:focus-within { 634 + --input-color: var(--color-success); 635 + } 636 + } 637 + &:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) { 638 + &, &:focus, &:checked, &[aria-checked="true"], &:focus-within { 639 + --input-color: var(--color-error); 640 + } 641 + & ~ .validator-hint { 642 + visibility: visible; 643 + color: var(--color-error); 644 + } 645 + } 646 + } 647 + &:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) { 648 + & ~ .validator-hint { 649 + display: revert-layer; 650 + } 651 + } 652 + } 623 653 .toggle { 624 654 @layer daisyui.l1.l2.l3 { 625 655 border: var(--border) solid currentColor; ··· 1947 1977 .mr-4 { 1948 1978 margin-right: calc(var(--spacing) * 4); 1949 1979 } 1980 + .fieldset-legend { 1981 + @layer daisyui.l1.l2.l3 { 1982 + margin-bottom: calc(0.25rem * -1); 1983 + display: flex; 1984 + align-items: center; 1985 + justify-content: space-between; 1986 + gap: calc(0.25rem * 2); 1987 + padding-block: calc(0.25rem * 2); 1988 + color: var(--color-base-content); 1989 + font-weight: 600; 1990 + } 1991 + } 1950 1992 .mb-1 { 1951 1993 margin-bottom: calc(var(--spacing) * 1); 1952 1994 } ··· 2068 2110 &:has(:nth-child(2)) { 2069 2111 grid-template-columns: auto minmax(auto, 1fr); 2070 2112 } 2113 + } 2114 + } 2115 + .fieldset { 2116 + @layer daisyui.l1.l2.l3 { 2117 + display: grid; 2118 + gap: calc(0.25rem * 1.5); 2119 + padding-block: calc(0.25rem * 1); 2120 + font-size: 0.75rem; 2121 + grid-template-columns: 1fr; 2122 + grid-auto-rows: max-content; 2071 2123 } 2072 2124 } 2073 2125 .chat { ··· 2242 2294 } 2243 2295 .items-center { 2244 2296 align-items: center; 2297 + } 2298 + .justify-between { 2299 + justify-content: space-between; 2245 2300 } 2246 2301 .justify-center { 2247 2302 justify-content: center; ··· 2461 2516 .text-base-content { 2462 2517 color: var(--color-base-content); 2463 2518 } 2519 + .text-base-content\/80 { 2520 + color: var(--color-base-content); 2521 + @supports (color: color-mix(in lab, red, red)) { 2522 + color: color-mix(in oklab, var(--color-base-content) 80%, transparent); 2523 + } 2524 + } 2464 2525 .text-base-content\/85 { 2465 2526 color: var(--color-base-content); 2466 2527 @supports (color: color-mix(in lab, red, red)) { 2467 2528 color: color-mix(in oklab, var(--color-base-content) 85%, transparent); 2468 2529 } 2530 + } 2531 + .text-error { 2532 + color: var(--color-error); 2469 2533 } 2470 2534 .text-neutral-content { 2471 2535 color: var(--color-neutral-content); ··· 2694 2758 @layer base { 2695 2759 *, ::after, ::before, ::backdrop, ::file-selector-button { 2696 2760 border-color: var(--color-gray-200, currentcolor); 2761 + } 2762 + } 2763 + .fieldset-legend { 2764 + @layer daisyui.l1.l2.l3 { 2765 + margin-bottom: calc(0.25rem * -1); 2766 + display: flex; 2767 + align-items: center; 2768 + justify-content: space-between; 2769 + gap: calc(0.25rem * 2); 2770 + padding-block: calc(0.25rem * 2); 2771 + color: var(--color-base-content); 2772 + font-weight: 600; 2773 + } 2774 + color: var(--color-base-content); 2775 + @supports (color: color-mix(in lab, red, red)) { 2776 + color: color-mix(in oklab, var(--color-base-content) 80%, transparent); 2697 2777 } 2698 2778 } 2699 2779 @layer base {
+3 -5
paw/templates/core/account_finish.html
··· 28 28 </div> 29 29 {% endif %} 30 30 31 - <div> 32 - <label class="label"> 33 - <span class="text-base label-text" for="{{ form.username.id_for_label }}">{% trans 'Username' %}</span> 34 - </label> 31 + <fieldset class="fieldset"> 32 + <legend class="fieldset-legend">{% trans 'Username' %}</legend> 35 33 {{ form.username }} 36 - </div> 34 + </fieldset> 37 35 <div class="flex justify-end mt-4"> 38 36 <button type="submit" name="account_finish" class="btn btn-success">{% trans 'Save' %}</button> 39 37 </div>
+10 -18
paw/templates/core/login.html
··· 4 4 {% load i18n %} 5 5 <div class="self-center w-full max-w-xl mx-auto p-4"> 6 6 <div class="flex items-center"> 7 - <h1 class="text-3xl font-bold p-2">{% trans 'Log In' %}</h1> 7 + <h1 class="text-3xl font-bold p-2 text-base-content/80">{% trans 'Log In' %}</h1> 8 8 <div class="flex-grow"></div> 9 9 <a href="{% url 'register' %}" class="btn btn-sm btn-neutral">{% trans 'Register Account' %}</a> 10 10 </div> ··· 19 19 <span>{{ form.non_field_errors }}</span> 20 20 </div> 21 21 {% endif %} 22 - 23 - <div> 24 - <label class="label"> 25 - <span class="text-base label-text" for="{{ form.username.id_for_label }}">{% trans 'Username' %}</span> 26 - </label> 27 - <input type="text" name="username" placeholder="Username" class="w-full input " /> 28 - </div> 29 - <label class="form-control w-full"> 30 - <div class="label"> 31 - <span class="text-base label-text" for="{{ form.password.id_for_label }}">{% trans 'Password' %}</span> 32 - </div> 33 - <input type="password" name="password" placeholder="Enter Password" 34 - class="w-full input " /> 35 - <div class="label"> 36 - <span class="label-text-alt"><a href="" class="link">{% trans 'Password Reset' %}</a></span> 37 - </div> 38 - </label> 22 + <fieldset class="fieldset"> 23 + <legend class="fieldset-legend text-base-content/80">{% trans 'Username' %}</legend> 24 + <input type="text" name="username" placeholder="Username" class="w-full input" /> 25 + </fieldset> 26 + <fieldset class="fieldset"> 27 + <legend class="fieldset-legend text-base-content/80">{% trans 'Password' %}</legend> 28 + <input type="password" name="password" placeholder="Enter Password" class="w-full input" /> 29 + <p class="label-text-alt"><a href="" class="link link-accent">{% trans 'Password Reset' %}</a></p> 30 + </fieldset> 39 31 <div class="flex justify-end mt-2"> 40 32 <button type="submit" class="btn btn-accent">{% trans 'Log In' %}</button> 41 33 </div>
+15 -22
paw/templates/core/register.html
··· 19 19 </div> 20 20 {% endif %} 21 21 22 - <div> 23 - <label class="label"> 24 - <span class="text-base label-text" for="{{ form.username.id_for_label }}">{% trans 'Username' %}</span> 25 - </label> 22 + <fieldset class="fieldset"> 23 + <legend class="fieldset-legend text-base-content/80">{% trans 'Username' %}</legend> 26 24 {{ form.username }} 27 - </div> 28 - <div> 29 - <label class="label"> 30 - <span class="text-base label-text" for="{{ form.email.id_for_label }}">{% trans '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 }}">{% trans '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 }}">{% trans 'Confirm Password' %}</span> 43 - </label> 25 + </fieldset> 26 + 27 + <fieldset class="fieldset"> 28 + <legend class="fieldset-legend text-base-content/80">{% trans 'Email Address' %}</legend> 29 + {{ form.email }} 30 + </fieldset> 31 + <fieldset class="fieldset"> 32 + <legend class="fieldset-legend text-base-content/80">{% trans 'Password' %}</legend> 33 + {{ form.password }} 34 + </fieldset> 35 + <fieldset class="fieldset"> 36 + <legend class="fieldset-legend text-base-content/80">{% trans 'Confirm Password' %}</legend> 44 37 {{ form.password_confirm }} 45 - </div> 38 + </fieldset> 46 39 <div class="flex justify-end mt-4"> 47 40 <button type="submit" class="btn btn-accent">{% trans 'Register' %}</button> 48 41 </div>
+28 -32
paw/templates/core/settings.html
··· 5 5 {% load filters %} 6 6 <div class="w-full max-w-4xl mx-auto p-8"> 7 7 <h1 class="text-2xl font-bold mb-4">{% trans 'Settings' %}</h1> 8 - <form method="post" enctype="multipart/form-data"> 8 + <form method="post" enctype="multipart/form-data" class="flex flex-col gap-2"> 9 9 {% csrf_token %} 10 - <label class="form-control w-full mb-2"> 11 - <div class="label"> 12 - <span for="{{ form.email.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Mail Address' %}</span> 13 - </div> 10 + <fieldset class="fieldset"> 11 + <legend class="fieldset-legend">{% trans 'Mail Address' %}</legend> 14 12 {% if request.user.googlessouser %} 15 13 {{ form.email|add_attr:'class:input w-full input-disabled,readonly:' }} 16 14 {% else %} 17 15 {{ form.email }} 18 16 {% endif %} 19 - </label> 20 - 21 - <label class="form-control w-full mb-2"> 22 - <div class="label"> 23 - <span for="{{ form.language.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Language' %}</span> 24 - </div> 25 - {{ form.language }} 26 - </label> 17 + </fieldset> 18 + <fieldset class="fieldset"> 19 + <legend class="fieldset-legend">{% trans 'Language' %}</legend> 20 + {{ form.language }} 21 + </fieldset> 27 22 28 - <div class="form-control w-full mb-2"> 29 - <label class="label cursor-pointer"> 30 - <span class="label-text font-semibold text-base-content" for="{{ form.use_darkmode.id_for_label }}">{% trans 'Use Darkmode' %}</span> 31 - {{ form.use_darkmode }} 32 - </label> 33 - </div> 34 - 35 - <div class="form-control w-full mb-2"> 36 - <label class="label cursor-pointer"> 37 - <span class="label-text font-semibold text-base-content" for="{{ form.receive_email_notifications.id_for_label }}">{% trans 'Receive Email Notifications' %}</span> 23 + <fieldset class="fieldset"> 24 + <label class="label cursor-pointer flex flex-row gap-2 justify-between items-center"> 25 + <span class="font-semibold text-base text-base-content" for="{{ form.use_darkmode.id_for_label }}">{% trans 'Use Darkmode' %}</span> 26 + {{ form.use_darkmode }} 27 + </label> 28 + </fieldset> 29 + <fieldset class="fieldset"> 30 + <label class="label cursor-pointer flex flex-row gap-2 justify-between items-center"> 31 + <span class="font-semibold text-base text-base-content" for="{{ form.receive_email_notifications.id_for_label }}">{% trans 'Receive Email Notifications' %}</span> 38 32 {{ form.receive_email_notifications }} 39 33 </label> 40 - </div> 34 + </fieldset> 41 35 42 - <label class="form-control w-full mb-2"> 43 - <div class="label"> 44 - <span for="{{ form.profile_picture.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Profile Picture' %}</span> 45 - </div> 46 - {{ form.profile_picture }} 47 - </label> 36 + <fieldset class="fieldset"> 37 + <legend class="fieldset-legend">{% trans 'Profile Picture' %}</legend> 38 + {{ form.profile_picture }} 39 + </fieldset> 48 40 49 - <h2 class="text-xl font-semibold mt-8 mb-2">{% trans 'Contact' %}</h2> 50 - <label class="input flex items-center gap-2"> 41 + <div> 42 + <h2 class="text-xl font-semibold mt-8 mb-2">{% trans 'Contact' %}</h2> 43 + <fieldset class="fieldset"> 44 + <label class="input w-full"> 51 45 <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 opacity-70" viewBox="0 0 24 24" stroke-width="3" 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> 52 46 {{ form.telegram_username }} 53 47 </label> 48 + </fieldset> 49 + </div> 54 50 <div class="flex justify-end items-center mt-4"> 55 51 <button type="submit" class="btn btn-success">{% trans 'Save' %}</button> 56 52 </div>
+1 -1
paw/templates/placeholder.html
··· 6 6 <select class="select select-sm select-bordered"> 7 7 <option>None</option> 8 8 </select> 9 - <input type="file" class="file-input w-full max-w-xs" /> 9 + <input type="file" class="file-input w-full max-w-xs" /> 10 10 <input type="text" class="grow" placeholder="Username" /> 11 11 <div class="form-control"> 12 12 <label class="label cursor-pointer">
+29 -30
paw/templates/ticketing/create_ticket.html
··· 3 3 {% load i18n %} 4 4 <div class="w-full max-w-4xl mx-auto p-8"> 5 5 <h1 class="text-2xl font-bold mb-4">{% trans 'Create a new ticket' %}</h1> 6 - <form action="" method="post" enctype="multipart/form-data" x-data="{ submitting: false }" @submit="submitting = true"> 6 + <form action="" method="post" class="flex flex-col gap-2" enctype="multipart/form-data" x-data="{ submitting: false }" @submit="submitting = true"> 7 7 {% csrf_token %} 8 8 {{ form.non_field_errors }} 9 - <label class="form-control mb-2"> 9 + <fieldset class="fieldset"> 10 10 {{ form.category.errors }} 11 - <div class="label"> 12 - <span for="{{ form.category.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Category' %}</span> 13 - </div> 11 + <legend class="fieldset-legend">{% trans 'Category' %}</legend> 14 12 {{ form.category }} 15 - </label> 16 - <label class="form-control mb-2"> 17 - {{ form.title.errors }} 18 - <div class="label"> 19 - <span for="{{ form.title.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Title' %}</span> 20 - </div> 13 + </fieldset> 14 + <fieldset class="fieldset"> 15 + <legend class="fieldset-legend">{% trans 'Title' %}</legend> 21 16 {{ form.title }} 22 - </label> 23 - <label class="form-control mb-2"> 24 - {{ form.description.errors }} 25 - <div class="label"> 26 - <span for="{{ form.description.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Description' %}</span> 27 - </div> 17 + {% if form.title.errors %} 18 + <div class="text-error">{{ form.title.errors }}</div> 19 + {% endif %} 20 + </fieldset> 21 + 22 + <fieldset class="fieldset"> 23 + <legend class="fieldset-legend">{% trans 'Description' %}</legend> 28 24 {{ form.description }} 29 - </label> 25 + {% if form.description.errors %} 26 + <div class="text-error">{{ form.description.errors }}</div> 27 + {% endif %} 28 + </fieldset> 30 29 31 - <label class="form-control mb-2"> 32 - {{ form.attachments.errors }} 33 - <div class="label"> 34 - <span for="{{ form.attachments.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Attachments' %}</span> 35 - </div> 30 + <fieldset class="fieldset"> 31 + <legend class="fieldset-legend">{% trans 'Attachments' %}</legend> 36 32 {{ form.attachments }} 37 - </label> 33 + {% if form.attachments.errors %} 34 + <div class="text-error">{{ form.attachments.errors }}</div> 35 + {% endif %} 36 + </fieldset> 38 37 39 38 {% if has_closed_tickets %} 40 - <label class="form-control mb-2"> 41 - {{ form.follow_up_to.errors }} 42 - <div class="label"> 43 - <span for="{{ form.follow_up_to.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Create as follow-up to a closed ticket' %}</span> 44 - </div> 39 + <fieldset class="fieldset"> 40 + <legend class="fieldset-legend">{% trans 'Create as follow-up to a closed ticket' %}</legend> 45 41 {{ form.follow_up_to }} 46 - </label> 42 + {% if form.follow_up_to.errors %} 43 + <div class="text-error">{{ form.follow_up_to.errors }}</div> 44 + {% endif %} 45 + </fieldset> 47 46 {% endif %} 48 47 49 48 <div class="flex justify-end items-center mt-4">
+11 -9
paw/templates/ticketing/ticket_detail.html
··· 72 72 <form action="" method="post"> 73 73 {% csrf_token %} 74 74 <div class="flex justify-end items-center mt-4 mb-2"> 75 - <div class="form-control w-full max-w-xs"> 75 + <div class="w-full max-w-xs"> 76 76 {{ template_form.template_select }} 77 77 </div> 78 78 <button type="submit" name="apply_template" class="btn btn-sm btn-success ml-2">{% trans 'Apply Template' %}</button> ··· 83 83 {% csrf_token %} 84 84 {{ form.non_field_errors }} 85 85 86 - <label class="form-control"> 87 - {{ form.text.errors }} 86 + <fieldset class="fieldset"> 88 87 {{ form.text }} 89 - </label> 88 + {% if form.text.errors %} 89 + <div class="text-error">{{ form.text.errors }}</div> 90 + {% endif %} 91 + </fieldset> 90 92 91 93 <div class="flex items-center mt-4"> 92 - <div class="form-control w-full max-w-lg mr-4"> 94 + <fieldset class="fieldset w-full max-w-lg mr-4"> 93 95 {{ form.attachments }} 94 - </div> 96 + </fieldset> 95 97 <div class="grow"></div> 96 98 <button type="submit" name="submit" class="btn btn-success">{% trans 'Add Comment' %}</button> 97 99 {% if can_edit %} ··· 101 103 102 104 <div class="flex justify-end"> 103 105 {% if can_edit %} 104 - <div class="form-control mt-2"> 106 + <fieldset class="fieldset mt-2"> 105 107 <label for="{{ form.hidden_from_client.id_for_label }}" class="cursor-pointer label"> 106 108 {{ form.hidden_from_client }} 107 - <span class="ml-2 label-text">{% trans 'Make this an internal comment' %}</span> 109 + <span class="ml-2 text-base label-text">{% trans 'Make this an internal comment' %}</span> 108 110 </label> 109 - </div> 111 + </fieldset> 110 112 {% endif %} 111 113 </div> 112 114 </form>
+4
theme/input.css
··· 37 37 border-color: var(--color-gray-200, currentcolor); 38 38 } 39 39 } 40 + 41 + .fieldset-legend { 42 + @apply fieldset-legend text-base-content/80; 43 + }
+2 -2
ticketing/forms.py
··· 55 55 56 56 class CommentForm(forms.Form): 57 57 text = forms.CharField(widget=forms.Textarea( 58 - attrs={'class': 'textarea h-32', 'placeholder': 'Enter your comment here...'})) 58 + attrs={'class': 'textarea h-32 w-full', 'placeholder': 'Enter your comment here...'})) 59 59 hidden_from_client = forms.BooleanField(widget=forms.CheckboxInput( 60 60 attrs={'class': 'checkbox checkbox-secondary'}), required=False) 61 61 ··· 70 70 model = Ticket 71 71 fields = ['title', 'description', 'category', 'follow_up_to'] 72 72 widgets = { 73 - 'title': forms.TextInput(attrs={'class': 'input w-full', 'placeholder': _('Please enter a title'), 'aria-label': _('Title')}), 73 + 'title': forms.TextInput(attrs={'class': 'input w-full', 'placeholder': _('Please enter a title'), 'aria-label': _('Title')}), 74 74 'description': forms.Textarea(attrs={'class': 'textarea h-32 w-full', 'placeholder': _('Please describe your issue'), 'aria-label': _('Description')}), 75 75 'category': forms.Select(attrs={'class': 'select w-full'}), 76 76 'follow_up_to': forms.Select(attrs={'class': 'select w-full'}),