Housekeeping

* Updated flowbite to 4.x
* Start revamping styles
* Remove unused GraphQL code
* Make some templates more robuts
This commit is contained in:
2026-02-17 22:14:16 +01:00
parent 277ecd1b55
commit 996c0107c9
19 changed files with 2191 additions and 481 deletions
+37 -57
View File
@@ -4,7 +4,8 @@
@plugin '@tailwindcss/forms';
@plugin 'flowbite/plugin';
@source '../node_modules/flowbite/**/*.js';
@source "../node_modules/flowbite";
@import "flowbite/src/themes/default";
@custom-variant dark (&:is(.dark *));
@@ -25,6 +26,7 @@
--color-background: #1f2937;
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
@@ -103,16 +105,6 @@
font-style: normal;
}
/* a:hover {
text-decoration-color: #ff4400;
color: rgb(254, 185, 160);
transition: all 0.2s ease-out;
} */
/* form label {
@apply dark:text-slate-400;
} */
.responsive-table {
@apply dark:text-white mx-auto table-fixed;
}
@@ -135,38 +127,17 @@
}
}
/* form input,
select,
textarea {
@apply dark:border dark:border-slate-900 dark:bg-slate-500 dark:text-slate-100;
} */
form input:disabled,
select:disabled,
textarea:disabled {
@apply dark:bg-slate-800 dark:text-slate-500 cursor-not-allowed;
@apply cursor-not-allowed bg-neutral-secondary-strong text-fg-disabled;
}
.errorlist {
@apply mt-4 mb-1 pl-3 py-2 bg-red-600 text-slate-200 w-[300px];
}
/* @media screen and (min-width: 768px) {
form input,
select,
textarea {
width: 300px;
}
} */
/* @media screen and (max-width: 768px) {
form input,
select,
textarea {
width: 150px;
}
} */
#button-container button {
@apply mx-1;
}
@@ -207,34 +178,43 @@ textarea:disabled {
padding-left: 1em;
}
/* .truncate-container {
@apply inline-block relative;
a {
@apply inline-block truncate max-w-20char transition-all group-hover:absolute group-hover:max-w-none group-hover:-top-8 group-hover:-left-6 group-hover:min-w-60 group-hover:px-6 group-hover:py-3.5 group-hover:bg-purple-600 group-hover:rounded-sm group-hover:outline-dashed group-hover:outline-purple-400 group-hover:outline-4;
#add-form {
.form-row-button-group {
display: flex;
flex-direction: row;
@apply gap-0 p-0;
button {
@apply mr-0;
&:first-child {
@apply rounded-e-none;
}
&:nth-child(2) {
@apply rounded-none;
}
&:last-child {
@apply rounded-s-none;
}
}
}
} */
label {
@apply dark:text-slate-500;
@apply mb-2.5 text-sm font-medium text-heading;
}
[type="text"], [type="password"], [type="datetime-local"], [type="datetime"], [type="date"], [type="number"], select, textarea {
@apply dark:bg-slate-600 dark:text-slate-300;
input:not([type="checkbox"]) {
@apply mb-3 bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand block w-full px-3 py-2.5 shadow-xs placeholder:text-body;
}
[type="submit"] {
@apply dark:text-white font-bold dark:bg-blue-600 px-4 py-2;
input[type="checkbox"] {
@apply w-4 h-4 border border-default-medium rounded-xs bg-neutral-secondary-medium focus:ring-2 focus:ring-brand-soft;
}
form div label {
@apply dark:text-white;
select {
@apply w-full px-3 py-2.5 bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand shadow-xs placeholder:text-body;
}
form div {
@apply flex flex-col;
textarea {
@apply bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand block w-full p-3.5 shadow-xs placeholder:text-body;
}
:has(> label + input[type="checkbox"]) {
@apply mb-3 mt-6;
display: flex;
flex-direction: row;
justify-content: space-between;
}
div [type="submit"] {
@apply mt-3;
}
+6 -5
View File
@@ -1,7 +1,8 @@
from graphene_django import DjangoObjectType
from games.models import Device as DeviceModel
from games.models import Edition as EditionModel
# from games.models import Edition as EditionModel
from games.models import Game as GameModel
from games.models import Platform as PlatformModel
from games.models import Purchase as PurchaseModel
@@ -14,10 +15,10 @@ class Game(DjangoObjectType):
fields = "__all__"
class Edition(DjangoObjectType):
class Meta:
model = EditionModel
fields = "__all__"
# class Edition(DjangoObjectType):
# class Meta:
# model = EditionModel
# fields = "__all__"
class Purchase(DjangoObjectType):
+4
View File
@@ -232,6 +232,10 @@ class Purchase(models.Model):
or self.price_currency != purchase_to_compare.price_currency
)
def refund(self):
self.date_refunded = timezone.now()
self.save()
def save(self, *args, **kwargs):
if self.type != Purchase.GAME and not self.related_purchase:
raise ValidationError(
-2
View File
@@ -3,7 +3,6 @@ import graphene
from games.graphql.mutations import GameMutation
from games.graphql.queries import (
DeviceQuery,
EditionQuery,
GameQuery,
PlatformQuery,
PurchaseQuery,
@@ -13,7 +12,6 @@ from games.graphql.queries import (
class Query(
GameQuery,
EditionQuery,
DeviceQuery,
PlatformQuery,
PurchaseQuery,
+2032 -356
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -1,7 +1,9 @@
<c-layouts.add>
<c-slot name="additional_row">
<input type="submit"
<c-button type="submit" color="gray"
name="submit_and_redirect"
value="Submit & Create Purchase" />
>
Submit & Create Purchase
</c-button>
</c-slot>
</c-layouts.add>
+4 -2
View File
@@ -3,9 +3,11 @@
<tr>
<td></td>
<td>
<input type="submit"
<c-button type="submit"
color="gray"
name="submit_and_redirect"
value="Submit & Create Session" />
value="Submit & Create Session">
</c-button>
</td>
</tr>
</c-slot>
+25 -23
View File
@@ -1,36 +1,38 @@
<c-layouts.add>
<c-slot name="form_content">
<form method="post" enctype="multipart/form-data">
<table class="mx-auto">
<div class="max-width-container">
<div id="add-form" class="form-container max-w-xl mx-auto">
<form method="post" enctype="multipart/form-data" class="">
{% csrf_token %}
{% for field in form %}
<tr>
<th>{{ field.label_tag }}</th>
<div>
{{ field.label_tag }}
{% if field.name == "note" %}
<td>{{ field }}</td>
{{ field }}
{% else %}
<td>{{ field }}</td>
{{ field }}
{% endif %}
{% if field.name == "timestamp_start" or field.name == "timestamp_end" %}
<td>
<div class="basic-button-container" hx-boost="false">
<button class="basic-button" data-target="{{ field.name }}" data-type="now">Set to now</button>
<button class="basic-button"
data-target="{{ field.name }}"
data-type="toggle">Toggle text</button>
<button class="basic-button" data-target="{{ field.name }}" data-type="copy">Copy</button>
</div>
</td>
<span class="form-row-button-group flex-row gap-3 justify-start mt-3" hx-boost="false">
<c-button data-target="{{ field.name }}" data-type="now" size="xs">Set to now</c-button>
<c-button data-target="{{ field.name }}" data-type="toggle" size="xs">Toggle text</c-button>
<c-button data-target="{{ field.name }}" data-type="copy" size="xs">
Copy {%if field.name == "timestamp_start" %}start{% else %}end{% endif %} value to {%if field.name == "timestamp_start" %}end{% else %}start{% endif %}
</c-button>
</span>
{% endif %}
</tr>
</div>
{% endfor %}
<tr>
<td></td>
<td>
<input type="submit" value="Submit" />
</td>
</tr>
</table>
<div>
<c-button type="submit">
Submit
</c-button>
</div>
<div class="submit-button-container">
{{ additional_row }}
</div>
</form>
</div>
</div>
</c-slot>
</c-layouts.add>
+7 -2
View File
@@ -1,6 +1,11 @@
<c-vars color="blue" size="base" type="button" />
<button type="{{ type }}"
<button
hx_get="{{ hx_get }}"
hx_target="{{ hx_target }}"
hx_swap="{{ hx_swap }}"
type="{{ type }}"
title="{{ title }}"
class="{{ class }} {% if color == "blue" %} bg-blue-700 dark:bg-blue-600 dark:focus:ring-blue-800 dark:hover:bg-blue-700 focus:ring-blue-300 hover:bg-blue-800 text-white {% elif color == "red" %} bg-red-700 dark:bg-red-600 dark:focus:ring-red-900 dark:hover:bg-red-700 focus:ring-red-300 hover:bg-red-800 text-white {% elif color == "gray" %} bg-white border-gray-200 dark:bg-gray-800 dark:border-gray-600 dark:focus:ring-gray-700 dark:hover:bg-gray-700 dark:hover:text-white dark:text-gray-400 focus:ring-gray-100 hover:bg-gray-100 hover:text-blue-700 text-gray-900 border {% elif color == "green" %} bg-green-700 dark:bg-green-600 dark:focus:ring-green-800 dark:hover:bg-green-700 focus:ring-green-300 hover:bg-green-800 text-white {% endif %} focus:outline-hidden focus:ring-4 font-medium mb-2 me-2 rounded-lg {% if size == "xs" %} px-3 py-2 text-xs {% elif size == "sm" %} px-3 py-2 text-sm {% elif size == "base" %} px-5 py-2.5 text-sm {% elif size == "lg" %} px-5 py-3 text-base {% elif size == "xl" %} px-6 py-3.5 text-base {% endif %} {% if icon %} inline-flex text-center items-center gap-2 {% else %} {% endif %} ">
onclick="{{ onclick }}"
class="{% if class %}{{ class }} {%else%}{%endif%}{% if color == "blue" %}text-white bg-brand box-border border border-transparent hover:bg-brand-strong focus:ring-4 focus:ring-brand-medium {% elif color == "red" %} bg-red-700 dark:bg-red-600 dark:focus:ring-red-900 dark:hover:bg-red-700 focus:ring-red-300 hover:bg-red-800 text-white {% elif color == "gray" %} bg-white border-gray-200 dark:bg-gray-800 dark:border-gray-600 dark:focus:ring-gray-700 dark:hover:bg-gray-700 dark:hover:text-white dark:text-gray-400 focus:ring-gray-100 hover:bg-gray-100 hover:text-blue-700 text-gray-900 border {% elif color == "green" %} bg-green-700 dark:bg-green-600 dark:focus:ring-green-800 dark:hover:bg-green-700 focus:ring-green-300 hover:bg-green-800 text-white {% endif %} leading-5 focus:outline-hidden focus:ring-4 font-medium mb-2 me-2 rounded-base {% if size == "xs" %} px-3 py-2 text-xs shadow-xs {% elif size == "sm" %} px-3 py-2 text-sm {% elif size == "base" %} px-5 py-2.5 text-sm {% elif size == "lg" %} px-5 py-3 text-base {% elif size == "xl" %} px-6 py-3.5 text-base {% endif %} {% if icon %} inline-flex text-center items-center gap-2 {% else %} {% endif %} ">
{{ slot }}
</button>
+1 -1
View File
@@ -2,7 +2,7 @@
{% if slot %}{{ slot }}{% endif %}
{% for button in buttons %}
{% if button.slot %}
<c-button-group-button-sm :href=button.href :slot=button.slot :color=button.color :hover=button.hover :title=button.title />
<c-button-group-button-sm :href=button.href :slot=button.slot :color=button.color :hover=button.hover :title=button.title :hx_get=button.hx_get :hx_target=button.hx_target :hx_swap=button.hx_swap />
{% endif %}
{% endfor %}
</div>
@@ -1,5 +1,7 @@
<c-vars color="gray" />
<a href="{{ href }}"
{% if hx_get %}hx-get="{{ hx_get }}"{% endif %}
{% if hx_target %}hx-target="{{ hx_target }}"{% endif %}
class="[&:first-of-type_button]:rounded-s-lg [&:last-of-type_button]:rounded-e-lg">
{% if color == "gray" %}
<button type="button"
+6 -2
View File
@@ -4,11 +4,15 @@
{{ form_content }}
{% else %}
<div class="max-width-container">
<div class="form-container max-w-xl mx-auto">
<div id="add-form" class="form-container max-w-xl mx-auto">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_div }}
<div><input type="submit" value="Submit" /></div>
<div>
<c-button type="submit" class="mt-3">
Submit
</c-button>
</div>
<div class="submit-button-container">
{{ additional_row }}
</div>
+3 -2
View File
@@ -25,7 +25,7 @@
}
</script>
</head>
<body hx-indicator="#indicator">
<body hx-indicator="#indicator" class="bg-neutral-primary">
<img id="indicator"
src="{% static 'icons/loading.png' %}"
class="absolute right-3 top-3 animate-spin htmx-indicator"
@@ -34,7 +34,7 @@
alt="loading indicator" />
<div class="flex flex-col min-h-screen">
{% include "navbar.html" %}
<div class="flex flex-1 flex-col dark:bg-gray-800 pt-8">{{ slot }}</div>
<div class="flex flex-1 flex-col pt-8 pb-16">{{ slot }}</div>
{% load version %}
<span class="fixed left-2 bottom-2 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date %})</span>
</div>
@@ -94,5 +94,6 @@
}
});
</script>
<div id="global-modal-container"></div>
</body>
</html>
+17
View File
@@ -0,0 +1,17 @@
<c-vars without_buttons="false" submit_text="Submit" close_text="Cancel" />
<div id="modal-container">
<div class="tt-modal fixed inset-0 bg-black/70 dark:bg-gray-600/50 overflow-y-auto h-full w-full flex items-center justify-center">
<div class="relative mx-auto p-5 border-accent border w-full max-w-md shadow-lg/50 rounded-md bg-white dark:bg-gray-900">
<div class="{{ container_class }}">
{{ slot }}
{% if not without_buttons %}
<div class="items-center mt-5">
<c-button color="blue" size="lg" type="submit" class="w-full">{{ submit_text }}</c-button>
<c-button color="gray" size="base" class="mt-0 w-full" onclick="this.closest('.tt-modal').remove()">{{ close_text }}</c-button>
</div>
{% endif %}
</form>
</div>
</div>
</div>
</div>
+14 -1
View File
@@ -1,5 +1,5 @@
<c-vars :name="id" />
<div class="pb-4 bg-white dark:bg-gray-900">
<!-- <div class="pb-4 bg-white dark:bg-gray-900">
<label for="table-search" class="sr-only">Search</label>
<div class="relative mt-1">
<div class="absolute inset-y-3 rtl:inset-r-0 start-0 flex items-center ps-3 pointer-events-none">
@@ -9,4 +9,17 @@
</div>
<input type="text" id="{{ id }}" name="{{ name }}" value="{{ search_string }}" class="block pt-2 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg w-80 bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="{% if placeholder %}{{ placeholder }}{% else %}Search{% endif %}">
</div>
</div> -->
<form class="max-w-md mx-auto">
<label for="search" class="block mb-2.5 text-sm font-medium text-heading sr-only ">Search</label>
<div class="relative">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 text-body" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m21 21-3.5-3.5M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg>
</div>
<input type="search" id="{{ id }}" name="{{ name }}" value="{{ search_string }}" class="block w-full p-3 ps-9 bg-neutral-secondary-medium border border-default-medium text-heading text-sm rounded-base focus:ring-brand focus:border-brand shadow-xs placeholder:text-body" placeholder="{% if placeholder %}{{ placeholder }}{% else %}Search{% endif %}" required />
<button type="button" class="absolute end-1.5 bottom-1.5 text-white bg-brand hover:bg-brand-strong box-border border border-transparent focus:ring-4 focus:ring-brand-medium shadow-xs font-medium leading-5 rounded text-xs px-3 py-1.5 focus:outline-none">Search</button>
</div>
</form>
+1 -1
View File
@@ -1,5 +1,5 @@
{% load static %}
<nav class="bg-white border-gray-200 dark:bg-gray-900 dark:border-gray-700">
<nav class="bg-neutral-primary-soft border-b border-default">
<div class="max-w-(--breakpoint-xl) flex flex-wrap items-center justify-between mx-auto p-4">
<a href="{% url 'index' %}"
class="flex items-center space-x-3 rtl:space-x-reverse">
+9 -6
View File
@@ -5,13 +5,13 @@ from django.core.paginator import Paginator
from django.http import (
HttpRequest,
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
)
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.http import require_POST
from common.components import A, Button, Icon, LinkedPurchase, PurchasePrice
from common.time import dateformat
@@ -202,11 +202,10 @@ def finish_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
def related_purchase_by_game(request: HttpRequest) -> HttpResponse:
games: list[str] = []
games = request.GET.getlist("games")
if not games:
return HttpResponseBadRequest("Invalid game_id")
if isinstance(games, int) or isinstance(games, str):
games = [games]
context = {}
if games:
form = PurchaseForm()
qs = Purchase.objects.filter(games__in=games, type=Purchase.GAME).order_by(
"games__sort_name"
@@ -216,4 +215,8 @@ def related_purchase_by_game(request: HttpRequest) -> HttpResponse:
first_option = qs.first()
if first_option:
form.fields["related_purchase"].initial = first_option.id
return render(request, "partials/related_purchase_field.html", {"form": form})
context["form"] = form
return render(request, "partials/related_purchase_field.html", context)
else:
# abort swap
return HttpResponse(status=204)
+2 -2
View File
@@ -208,9 +208,9 @@ def add_session(request: HttpRequest, game_id: int = 0) -> HttpResponse:
context["title"] = "Add New Session"
# TODO: re-add custom buttons #91
# context["script_name"] = "add_session.js"
context["script_name"] = "add_session.js"
context["form"] = form
return render(request, "add.html", context)
return render(request, "add_session.html", context)
@login_required
+1 -1
View File
@@ -8,6 +8,6 @@
},
"dependencies": {
"@tailwindcss/cli": "^4.1.18",
"flowbite": "^2.4.1"
"flowbite": "^4.0.1"
}
}