Housekeeping
* Updated flowbite to 4.x * Start revamping styles * Remove unused GraphQL code * Make some templates more robuts
This commit is contained in:
+43
-63
@@ -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 mb-2.5 text-sm font-medium text-heading;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
} */
|
||||
|
||||
label {
|
||||
@apply dark:text-slate-500;
|
||||
}
|
||||
|
||||
[type="text"], [type="password"], [type="datetime-local"], [type="datetime"], [type="date"], [type="number"], select, textarea {
|
||||
@apply dark:bg-slate-600 dark:text-slate-300;
|
||||
}
|
||||
|
||||
[type="submit"] {
|
||||
@apply dark:text-white font-bold dark:bg-blue-600 px-4 py-2;
|
||||
}
|
||||
|
||||
form div label {
|
||||
@apply dark:text-white;
|
||||
}
|
||||
|
||||
form div {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
div [type="submit"] {
|
||||
@apply mt-3;
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2033
-357
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</form>
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
</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,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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -8,6 +8,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/cli": "^4.1.18",
|
||||
"flowbite": "^2.4.1"
|
||||
"flowbite": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user