Compare commits
34
Commits
chore/bbs-bumps
...
ig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2ce30edd | ||
|
|
4484195ddb | ||
|
|
79fe5cac0f | ||
|
|
95d7d13f38 | ||
|
|
ce2cdfd409 | ||
|
|
b56d103ae7 | ||
|
|
56efe6760e | ||
|
|
ce31ef84cc | ||
|
|
491604c720 | ||
|
|
05d3dcbb3e | ||
|
|
8577fb9f11 | ||
|
|
a038340e4a | ||
|
|
34a01e9293 | ||
|
|
54dd21f1eb | ||
|
|
53ba5a05a1 | ||
|
|
f775446224 | ||
|
|
13cc0d92a2 | ||
|
|
7bfa587a77 | ||
|
|
3d91148b7e | ||
|
|
54bbd75730 | ||
|
|
06f43b06b8 | ||
|
|
ad116c2c28 | ||
|
|
eb960bdb84 | ||
|
|
f4c99153ca | ||
|
|
3e457de416 | ||
|
|
faca98b7f9 | ||
|
|
53ef262ba4 | ||
|
|
f4749e6a6e | ||
|
|
e5de16f227 | ||
|
|
eace79f012 | ||
|
|
9b903f42d6 | ||
|
|
e1eaf3df98 | ||
|
|
b79d9fc9c8 | ||
|
|
42f23b1d77 |
No files matched your search
@@ -0,0 +1 @@
|
||||
VITE_API_URL=http://localhost:3000
|
||||
+4
-1
@@ -11,4 +11,7 @@ CACHE/
|
||||
media/
|
||||
tmp/
|
||||
frontend/dist
|
||||
static/modern
|
||||
static/assets
|
||||
static/index.html
|
||||
.python-version
|
||||
.env
|
||||
+12
-1
@@ -2,7 +2,7 @@ variables:
|
||||
PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip"
|
||||
|
||||
default:
|
||||
image: nikolaik/python-nodejs:python3.9-nodejs16
|
||||
image: nikolaik/python-nodejs:python3.8-nodejs16
|
||||
cache:
|
||||
paths:
|
||||
- .cache/pip
|
||||
@@ -29,6 +29,17 @@ lint:
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
before_script:
|
||||
# Install ssh if not already
|
||||
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
- eval $(ssh-agent -s)
|
||||
# Make SSH key available
|
||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
|
||||
- mkdir -p ~/.ssh
|
||||
- chmod 700 ~/.ssh
|
||||
# Add known host keys
|
||||
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
|
||||
- chmod 644 ~/.ssh/known_hosts
|
||||
script:
|
||||
- ssh gitdeploy@dreamcast.neuf.no "$(< bin/deploy)"
|
||||
only:
|
||||
|
||||
@@ -9,6 +9,7 @@ poetry shell
|
||||
pre-commit install
|
||||
python manage.py migrate
|
||||
bin/run
|
||||
cp .env-default .env
|
||||
|
||||
# in another terminal
|
||||
cd frontend
|
||||
@@ -22,9 +23,8 @@ npm start
|
||||
|
||||
## TODO
|
||||
|
||||
- update emails
|
||||
- improve product listing (light purple background)
|
||||
- Stats: products over time
|
||||
- Light on dark colors
|
||||
- inventory views
|
||||
|
||||
|
||||
|
||||
Regular → Executable
File renamed without changes.
@@ -1,15 +1,22 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from main.models import *
|
||||
from bbs.models import *
|
||||
|
||||
|
||||
class ProductAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class CardAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "user", "card_id", "active")
|
||||
list_filter = ("active",)
|
||||
search_fields = ("user__username", "name")
|
||||
|
||||
|
||||
admin.site.register(Product, ProductAdmin)
|
||||
admin.site.register(InventoryTransaction)
|
||||
admin.site.register(Order)
|
||||
admin.site.register(OrderLine)
|
||||
admin.site.register(Transaction)
|
||||
admin.site.register(UserProfile)
|
||||
admin.site.register(Card, CardAdmin)
|
||||
Executable → Regular
File renamed without changes.
@@ -1,3 +1,5 @@
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
|
||||
|
||||
@@ -17,6 +19,6 @@ class FieldError(Error):
|
||||
|
||||
@strawberry.type
|
||||
class FormErrors:
|
||||
fields: list[FieldError]
|
||||
fields: List[FieldError]
|
||||
message: str = ""
|
||||
error: bool = True
|
||||
File renamed without changes.
@@ -1,3 +1,5 @@
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
@@ -5,10 +7,10 @@ from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from strawberry import ID
|
||||
|
||||
from main import models
|
||||
from main.api.errors import Error, FieldError, FormErrors
|
||||
from main.api.types.inventory import Transaction
|
||||
from main.api.types.shop import Order
|
||||
from bbs import models
|
||||
from bbs.api.errors import Error, FieldError, FormErrors
|
||||
from bbs.api.types.inventory import Transaction
|
||||
from bbs.api.types.shop import Order
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
@@ -34,7 +36,7 @@ CreateOrderResponse = strawberry.union("CreateOrderResponse", [CreateOrderSucces
|
||||
|
||||
@strawberry.mutation
|
||||
@transaction.atomic
|
||||
def create_order(customer_id: ID, order_lines: list[OrderLineInput], is_external: bool = False) -> CreateOrderResponse:
|
||||
def create_order(customer_id: ID, order_lines: List[OrderLineInput], is_external: bool = False) -> CreateOrderResponse:
|
||||
# clean and validate
|
||||
try:
|
||||
user = UserModel.objects.get(pk=customer_id)
|
||||
@@ -4,8 +4,8 @@ from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
from strawberry_django import auto
|
||||
|
||||
from main import models
|
||||
from main.api.types.users import User
|
||||
from bbs import models
|
||||
from bbs.api.types.users import User
|
||||
|
||||
DjangoUser = get_user_model()
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,11 @@
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from bbs.api.types.inventory import Transaction, TransactionOrdering
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class InventoryQueries:
|
||||
transaction_list: List[Transaction] = strawberry_django.field(pagination=True, order=TransactionOrdering)
|
||||
@@ -1,15 +1,17 @@
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from main import models
|
||||
from main.api.types.shop import Order, OrderOrdering, OrderStats, OrderStatsByTime, Point, Product, ProductOrderStat
|
||||
from bbs import models
|
||||
from bbs.api.types.shop import Order, OrderOrdering, OrderStats, OrderStatsByTime, Point, Product, ProductOrderStat
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ShopQueries:
|
||||
all_products: list[Product] = strawberry_django.field()
|
||||
all_orders: list[Order] = strawberry_django.field()
|
||||
order_list: list[Order] = strawberry_django.field(pagination=True, order=OrderOrdering)
|
||||
all_products: List[Product] = strawberry_django.field()
|
||||
all_orders: List[Order] = strawberry_django.field()
|
||||
order_list: List[Order] = strawberry_django.field(pagination=True, order=OrderOrdering)
|
||||
|
||||
@strawberry.field
|
||||
def order_stats(self) -> OrderStats:
|
||||
@@ -24,7 +26,7 @@ class ShopQueries:
|
||||
)
|
||||
|
||||
@strawberry.field
|
||||
def product_stats(self) -> list[ProductOrderStat]:
|
||||
def product_stats(self) -> List[ProductOrderStat]:
|
||||
return [
|
||||
ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
|
||||
for stat in models.OrderLine.objects.product_order_stats().as_series()
|
||||
@@ -1,14 +1,16 @@
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from django.contrib.auth.models import User as DjangoUser
|
||||
from strawberry import ID
|
||||
|
||||
from main.api.types.users import User
|
||||
from bbs.api.types.users import User
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class UsersQueries:
|
||||
all_users: list[User] = strawberry_django.field()
|
||||
all_users: List[User] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def user(self, user_id: ID) -> User:
|
||||
@@ -0,0 +1,34 @@
|
||||
import strawberry
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from strawberry.extensions import Extension
|
||||
|
||||
from bbs.api.mutations.shop import ShopMutations
|
||||
from bbs.api.mutations.users import UsersMutations
|
||||
from bbs.api.queries.inventory import InventoryQueries
|
||||
from bbs.api.queries.shop import ShopQueries
|
||||
from bbs.api.queries.users import UsersQueries
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class Query(InventoryQueries, ShopQueries, UsersQueries):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class Mutation(ShopMutations, UsersMutations):
|
||||
pass
|
||||
|
||||
|
||||
class HandleNotFound(Extension):
|
||||
def on_request_end(self):
|
||||
result = self.execution_context.result
|
||||
if result.errors:
|
||||
for error in result.errors:
|
||||
if isinstance(error.original_error, ObjectDoesNotExist):
|
||||
error.extensions = {
|
||||
"code": "NOT_FOUND",
|
||||
"model": type(error.original_error).__qualname__.replace(".DoesNotExist", ""),
|
||||
}
|
||||
|
||||
|
||||
schema = strawberry.Schema(query=Query, mutation=Mutation, extensions=[HandleNotFound])
|
||||
File renamed without changes.
@@ -1,8 +1,8 @@
|
||||
import strawberry_django
|
||||
from strawberry_django import auto
|
||||
|
||||
from main import models
|
||||
from main.api.types.users import User
|
||||
from bbs import models
|
||||
from bbs.api.types.users import User
|
||||
|
||||
|
||||
@strawberry_django.ordering.order(models.Transaction)
|
||||
@@ -1,9 +1,11 @@
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry_django import auto
|
||||
|
||||
from main import models
|
||||
from main.api.types.utils import JSONScalar
|
||||
from bbs import models
|
||||
from bbs.api.types.utils import JSONScalar
|
||||
|
||||
|
||||
@strawberry_django.filters.filter(models.Product)
|
||||
@@ -31,10 +33,10 @@ class OrderOrdering:
|
||||
@strawberry_django.type(models.Order)
|
||||
class Order:
|
||||
id: auto
|
||||
customer: strawberry.LazyType["User", "main.api.types.users"]
|
||||
customer: strawberry.LazyType["User", "bbs.api.types.users"]
|
||||
order_sum: auto
|
||||
created: auto
|
||||
orderlines: list["OrderLine"]
|
||||
orderlines: List["OrderLine"]
|
||||
is_external: bool
|
||||
|
||||
|
||||
@@ -55,10 +57,10 @@ class OrderStatsByTime:
|
||||
|
||||
@strawberry.type
|
||||
class OrderStats:
|
||||
yearly: list[OrderStatsByTime]
|
||||
monthly: list[OrderStatsByTime]
|
||||
daily: list[OrderStatsByTime]
|
||||
hourly: list[OrderStatsByTime]
|
||||
yearly: List[OrderStatsByTime]
|
||||
monthly: List[OrderStatsByTime]
|
||||
daily: List[OrderStatsByTime]
|
||||
hourly: List[OrderStatsByTime]
|
||||
|
||||
|
||||
@strawberry.type
|
||||
@@ -70,4 +72,4 @@ class Point:
|
||||
@strawberry.type
|
||||
class ProductOrderStat:
|
||||
product_name: str
|
||||
data: list[Point]
|
||||
data: List[Point]
|
||||
@@ -1,12 +1,13 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from django.contrib.auth import get_user_model
|
||||
from strawberry_django import auto
|
||||
|
||||
from main import models
|
||||
from main.api.types.shop import Point, ProductOrderStat
|
||||
from bbs import models
|
||||
from bbs.api.types.shop import Point, ProductOrderStat
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
@@ -27,11 +28,11 @@ class UserProfile:
|
||||
order_sum_total: float
|
||||
|
||||
@strawberry.field()
|
||||
def top_months(self) -> list[TopMonth]:
|
||||
def top_months(self) -> List[TopMonth]:
|
||||
return [TopMonth(**month) for month in self.top_months()]
|
||||
|
||||
@strawberry.field()
|
||||
def product_order_stats(self) -> list[ProductOrderStat]:
|
||||
def product_order_stats(self) -> List[ProductOrderStat]:
|
||||
return [
|
||||
ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
|
||||
for stat in self.product_order_stats().as_series()
|
||||
File renamed without changes.
@@ -2,7 +2,7 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class BBSAppConfig(AppConfig):
|
||||
name = "main"
|
||||
name = "bbs"
|
||||
verbose_name = "Biceps Bar System"
|
||||
|
||||
def ready(self):
|
||||
@@ -1,6 +1,6 @@
|
||||
from django import forms
|
||||
|
||||
from main.models import InventoryTransaction, Product
|
||||
from bbs.models import InventoryTransaction, Product
|
||||
|
||||
|
||||
class InventoryTransactionForm(forms.ModelForm):
|
||||
File renamed without changes.
File renamed without changes.
+3
-3
@@ -1,10 +1,10 @@
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand, CommandParser
|
||||
from strawberry.printer import print_schema
|
||||
|
||||
from bbssite.settings import BASE_DIR
|
||||
from main.api.schema import schema as schema_symbol
|
||||
from bbs.api.schema import schema as schema_symbol
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -16,7 +16,7 @@ class Command(BaseCommand):
|
||||
def handle(self, *args, filename=None, **options):
|
||||
schema_output = print_schema(schema_symbol)
|
||||
if filename != "-":
|
||||
with Path(BASE_DIR, filename).open("w+") as fp:
|
||||
with Path(settings.BASE_DIR, filename).open("w+") as fp:
|
||||
fp.write(schema_output)
|
||||
self.stdout.write(self.style.SUCCESS("OK"))
|
||||
else:
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
from django.core.management.base import BaseCommand, CommandParser
|
||||
|
||||
from main.models import UserProfile
|
||||
from bbs.models import UserProfile
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -72,7 +72,7 @@ class Migration(migrations.Migration):
|
||||
models.ForeignKey(
|
||||
on_delete=models.CASCADE,
|
||||
related_name="orderlines",
|
||||
to="main.Order",
|
||||
to="bbs.Order",
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -157,7 +157,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name="orderline",
|
||||
name="product",
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name="orderlines", to="main.Product"),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name="orderlines", to="bbs.Product"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="inventorytransaction",
|
||||
@@ -166,7 +166,7 @@ class Migration(migrations.Migration):
|
||||
on_delete=models.CASCADE,
|
||||
related_name="transactions",
|
||||
verbose_name="Produkt",
|
||||
to="main.Product",
|
||||
to="bbs.Product",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
File renamed without changes.
@@ -146,8 +146,8 @@ class OrderLineManager(models.QuerySet):
|
||||
|
||||
|
||||
class OrderLine(models.Model):
|
||||
order = models.ForeignKey("main.Order", on_delete=models.CASCADE, related_name="orderlines")
|
||||
product = models.ForeignKey("main.Product", on_delete=models.CASCADE, related_name="orderlines")
|
||||
order = models.ForeignKey("bbs.Order", on_delete=models.CASCADE, related_name="orderlines")
|
||||
product = models.ForeignKey("bbs.Product", on_delete=models.CASCADE, related_name="orderlines")
|
||||
amount = models.IntegerField()
|
||||
unit_price = models.FloatField(verbose_name="Enhetspris")
|
||||
|
||||
@@ -184,7 +184,7 @@ class InventoryTransaction(models.Model):
|
||||
related_name="inventory_transactions",
|
||||
)
|
||||
product = models.ForeignKey(
|
||||
"main.Product",
|
||||
"bbs.Product",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="transactions",
|
||||
verbose_name="Produkt",
|
||||
@@ -245,3 +245,16 @@ class UserProfile(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user}"
|
||||
|
||||
|
||||
class Card(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="cards")
|
||||
card_id = models.CharField(max_length=32)
|
||||
name = models.CharField(max_length=64)
|
||||
# pin = models.IntegerField(max_length=4, blank=True, null=True)
|
||||
# valid_until = models.DateTimeField(blank=True, null=True)
|
||||
active = models.BooleanField(default=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
@@ -72,7 +72,7 @@ MIDDLEWARE = [
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "bbssite.urls"
|
||||
ROOT_URLCONF = "bbs.urls"
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.auth",
|
||||
@@ -85,9 +85,10 @@ INSTALLED_APPS = [
|
||||
"django_extensions",
|
||||
"easy_thumbnails",
|
||||
"corsheaders",
|
||||
"django_rename_app",
|
||||
]
|
||||
LOCAL_APPS = [
|
||||
"main",
|
||||
"bbs",
|
||||
]
|
||||
INSTALLED_APPS += LOCAL_APPS
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from django.test import Client
|
||||
from strawberry.django.test import GraphQLTestClient
|
||||
|
||||
from main.models import Order, Product
|
||||
from bbs.models import Order, Product
|
||||
|
||||
|
||||
@pytest.fixture(name="gql_client")
|
||||
Executable → Regular
+7
-4
@@ -2,16 +2,19 @@ from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.views import LoginView, LogoutView
|
||||
from django.urls import include, path, re_path
|
||||
from django.urls import path, re_path
|
||||
from strawberry.django.views import GraphQLView
|
||||
|
||||
from main import urls as main_urls
|
||||
from main.api.schema import schema
|
||||
from bbs.api.schema import schema
|
||||
from bbs.views.reports import inventory, inventory_add, report
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"", include(main_urls)),
|
||||
# TODO: migrate reports to SPA
|
||||
re_path(r"^inventory/$", inventory, name="inventory"),
|
||||
re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
|
||||
re_path(r"^report$", report, name="report"),
|
||||
path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"),
|
||||
path("accounts/logout/", LogoutView.as_view(), name="django.contrib.auth.views.logout"),
|
||||
path("admin/", admin.site.urls),
|
||||
File renamed without changes.
File renamed without changes.
@@ -4,8 +4,8 @@ from itertools import groupby
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
from main.forms import DateRangeForm, InventoryTransactionForm
|
||||
from main.models import InventoryTransaction, Order, Product, Transaction, UserProfile
|
||||
from bbs.forms import DateRangeForm, InventoryTransactionForm
|
||||
from bbs.models import InventoryTransaction, Order, Product, Transaction, UserProfile
|
||||
|
||||
|
||||
def report(request):
|
||||
@@ -2,6 +2,6 @@ import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbssite.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbs.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
cd frontend
|
||||
npm i
|
||||
npm run build
|
||||
+10
-2
@@ -1,14 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
export VITE_API_URL="https://bbs.neuf.no/api"
|
||||
PROJECT_PATH=/var/www/neuf.no/bbs
|
||||
PROCESS_NAME="$PROJECT.neuf.no"
|
||||
PROCESS_NAME="bbs.neuf.no"
|
||||
|
||||
echo "Deploying bbs to ${PROJECT_PATH}"
|
||||
cd "$PROJECT_PATH" || exit 1
|
||||
git pull
|
||||
|
||||
# Backend
|
||||
poetry install
|
||||
umask 022; poetry run python manage.py collectstatic --noinput # Collect static
|
||||
poetry run python manage.py migrate # Run DB migrations
|
||||
|
||||
# Reload
|
||||
# Frontend
|
||||
bin/build-frontend
|
||||
|
||||
# Reload backend
|
||||
echo "Reloading supervisor process ${PROCESS_NAME}"
|
||||
sudo /usr/bin/supervisorctl pid "$PROCESS_NAME" | xargs kill -HUP
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SRC_DIR="main bbssite"
|
||||
SRC_DIR="bbs"
|
||||
|
||||
black $SRC_DIR
|
||||
isort $SRC_DIR
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SRC_DIR="main bbssite"
|
||||
SRC_DIR="bbs"
|
||||
|
||||
black --check $SRC_DIR
|
||||
isort --diff $SRC_DIR
|
||||
@@ -1 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd frontend && npm run start
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BBS</title>
|
||||
</head>
|
||||
|
||||
Generated
+949
-984
File diff suppressed because it is too large.
Load diff
+21
-24
@@ -16,51 +16,48 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.5.8",
|
||||
"@apollo/client": "^3.5.10",
|
||||
"@nivo/bar": "^0.79.1",
|
||||
"@nivo/line": "^0.79.1",
|
||||
"@nivo/pie": "^0.79.1",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-icons": "^1.8.1",
|
||||
"clsx": "^1.1.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"graphql": "^16.3.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hook-form": "^7.27.1",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"react-hook-form": "^7.30.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"use-url-search-params": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.6.1",
|
||||
"@graphql-codegen/typed-document-node": "^2.2.3",
|
||||
"@graphql-codegen/typescript-operations": "^2.3.0",
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
"@graphql-codegen/typed-document-node": "^2.2.8",
|
||||
"@graphql-codegen/typescript-operations": "^2.3.5",
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"@types/bootstrap": "^5.1.9",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react": "^17.0.44",
|
||||
"@types/react-dom": "^17.0.15",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
||||
"@typescript-eslint/parser": "^5.11.0",
|
||||
"@vitejs/plugin-react": "^1.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
||||
"@typescript-eslint/parser": "^5.19.0",
|
||||
"@vitejs/plugin-react": "^1.3.1",
|
||||
"@vitejs/plugin-react-refresh": "^1.3.6",
|
||||
"eslint": "^8.9.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.4.0",
|
||||
"jest": "^27.5.1",
|
||||
"prettier": "^2.5.1",
|
||||
"stylelint": "^14.5.0",
|
||||
"prettier": "^2.6.2",
|
||||
"stylelint": "^14.7.0",
|
||||
"stylelint-config-idiomatic-order": "^8.1.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-recommended": "^7.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"stylelint-prettier": "^2.0.0",
|
||||
"typescript": "^4.5.5",
|
||||
"vite": "^2.8.1",
|
||||
"vite-plugin-svgr": "^1.0.1"
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^2.9.5",
|
||||
"vite-plugin-svgr": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.app {
|
||||
margin: 16px;
|
||||
margin: 1rem auto;
|
||||
max-width: 1220px;
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
|
||||
import styles from "./App.module.css";
|
||||
import { Routes } from "./Routes";
|
||||
import client from "./client";
|
||||
import { Footer } from "./components/Footer";
|
||||
import { Header } from "./components/Header";
|
||||
import { Toast } from "./components/Toast";
|
||||
import { ToastProvider } from "./components/ToastProvider";
|
||||
import { Wireframe } from "./components/Wireframe";
|
||||
|
||||
const App: React.FC = () => (
|
||||
const App = (): JSX.Element => (
|
||||
<ApolloProvider client={client}>
|
||||
<Router>
|
||||
<ToastProvider>
|
||||
<Header />
|
||||
<div className={styles.app}>
|
||||
<Routes />
|
||||
<Wireframe />
|
||||
</div>
|
||||
<Footer />
|
||||
<Toast />
|
||||
</ToastProvider>
|
||||
</Router>
|
||||
|
||||
+14
-10
@@ -1,17 +1,19 @@
|
||||
import React from "react";
|
||||
import { Route, Routes as ReactRouterRoutes } from "react-router-dom";
|
||||
|
||||
import { UserCreate } from "./features/auth/UserCreate";
|
||||
import { Deposit } from "./features/deposit/Deposit";
|
||||
import { Log } from "./features/log/Log";
|
||||
import { Register } from "./features/register/Register";
|
||||
import { OrderStats } from "./features/stats/OrderStats";
|
||||
import { ProductStats } from "./features/stats/ProductStats";
|
||||
import { Stats } from "./features/stats/Stats";
|
||||
import { UserDetail } from "./features/users/UserDetail";
|
||||
import { UserList } from "./features/users/UserList";
|
||||
const Splash = React.lazy(() => import("./components/Splash"));
|
||||
const UserCreate = React.lazy(() => import("./features/auth/UserCreate"));
|
||||
const Deposit = React.lazy(() => import("./features/deposit/Deposit"));
|
||||
const Log = React.lazy(() => import("./features/log/Log"));
|
||||
const Register = React.lazy(() => import("./features/register/Register"));
|
||||
const OrderStats = React.lazy(() => import("./features/stats/OrderStats"));
|
||||
const ProductStats = React.lazy(() => import("./features/stats/ProductStats"));
|
||||
const Stats = React.lazy(() => import("./features/stats/Stats"));
|
||||
const UserDetail = React.lazy(() => import("./features/users/UserDetail"));
|
||||
const UserList = React.lazy(() => import("./features/users/UserList"));
|
||||
|
||||
export const Routes: React.FC = () => (
|
||||
export const Routes = (): JSX.Element => (
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<ReactRouterRoutes>
|
||||
<Route path="/deposit" element={<Deposit />} />
|
||||
<Route path="/log" element={<Log />} />
|
||||
@@ -22,5 +24,7 @@ export const Routes: React.FC = () => (
|
||||
<Route path="/users/create" element={<UserCreate />} />
|
||||
<Route path="/user/:userId" element={<UserDetail />} />
|
||||
<Route path="/" element={<Register />} />
|
||||
<Route path="*" element={<Splash brandText="404" noOverlay />} />
|
||||
</ReactRouterRoutes>
|
||||
</React.Suspense>
|
||||
);
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
||||
|
||||
const client = new ApolloClient({
|
||||
uri: import.meta.env.VITE_API_URL || "http://localhost:8000/graphql/",
|
||||
uri: `${import.meta.env.VITE_API_URL}/graphql/`,
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
.footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 1rem;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.heart {
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.brick {
|
||||
position: relative;
|
||||
top: 7px;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
import { ReactComponent as Brick } from "../assets/brick.svg";
|
||||
import styles from "./Footer.module.css";
|
||||
|
||||
export const Footer = (): JSX.Element => {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
Made with <span className={styles.heart}>❤</span> by{" "}
|
||||
<span title="EDB-gjengen">
|
||||
<Brick className={styles.brick} />
|
||||
</span>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
.errorMessage {
|
||||
font-family: Rocket Rinder, monospace;
|
||||
font-size: 3rem;
|
||||
font-style: italic;
|
||||
color: var(--brand);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 30vh;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApolloError } from "@apollo/client";
|
||||
import React from "react";
|
||||
|
||||
import styles from "./GQLError.module.css";
|
||||
|
||||
const formatModel = (text: string) => {
|
||||
const withSpaces = text
|
||||
.replace(/([A-Z])/g, " $1")
|
||||
.toLocaleLowerCase()
|
||||
.trim();
|
||||
return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
|
||||
};
|
||||
|
||||
export const GQLError = ({ error }: { error: ApolloError }) => {
|
||||
if (error?.graphQLErrors?.length == 1) {
|
||||
const single = error?.graphQLErrors[0];
|
||||
if (single?.extensions?.code === "NOT_FOUND") {
|
||||
const modelPretty = formatModel(single?.extensions?.model as string);
|
||||
return <div className={styles.errorMessage}>{`${modelPretty} not found`}</div>;
|
||||
}
|
||||
}
|
||||
return <div className={styles.errorMessage}>Error...</div>;
|
||||
};
|
||||
@@ -1,13 +1,94 @@
|
||||
.logo {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 8px;
|
||||
.header {
|
||||
margin: 1rem auto;
|
||||
max-width: 1220px;
|
||||
}
|
||||
|
||||
.navIcon {
|
||||
.nav {
|
||||
display: flex;
|
||||
height: 5rem;
|
||||
align-items: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.navLink:after {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
left: 14px;
|
||||
display: block;
|
||||
height: 1px;
|
||||
background-color: rgba(243, 33, 61, 0.4);
|
||||
box-shadow: 0 0 4px rgba(243, 33, 61, 0.4), 0 0 11px rgba(243, 33, 61, 0.4), 0 0 19px rgba(243, 33, 61, 0.4);
|
||||
content: "";
|
||||
transform: rotate(-2.5deg);
|
||||
}
|
||||
|
||||
.navLink:hover:after {
|
||||
background-color: var(--brand);
|
||||
box-shadow: 0 0 4px var(--brand), 0 0 11px var(--brand), 0 0 19px var(--brand), 0 0 30px var(--brand);
|
||||
}
|
||||
|
||||
.logoNav {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.navItems {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.navLink {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border: 1px solid transparent;
|
||||
transition: color 0.1s ease-in-out;
|
||||
}
|
||||
.navLink:hover {
|
||||
color: white;
|
||||
}
|
||||
.active.active {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* FIXME: Duplicates Splash styles */
|
||||
.brand {
|
||||
padding-left: 1rem;
|
||||
color: var(--brand);
|
||||
font-family: Rocket Rinder, monospace;
|
||||
font-size: 3rem;
|
||||
font-style: italic;
|
||||
letter-spacing: 2px;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
position: relative;
|
||||
left: -8px;
|
||||
animation: flicker 1.5s infinite alternate;
|
||||
color: var(--interlace);
|
||||
font-family: Rocket Clouds DEMO, serif;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
transform: rotate(-6deg);
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0%,
|
||||
18%,
|
||||
22%,
|
||||
25%,
|
||||
53%,
|
||||
57%,
|
||||
100% {
|
||||
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8);
|
||||
}
|
||||
|
||||
20%,
|
||||
24%,
|
||||
55% {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,50 @@
|
||||
import { ReactComponent as BarChart } from "bootstrap-icons/icons/bar-chart.svg";
|
||||
import { ReactComponent as CardList } from "bootstrap-icons/icons/card-list.svg";
|
||||
import { ReactComponent as House } from "bootstrap-icons/icons/house.svg";
|
||||
import { ReactComponent as PeopleIcon } from "bootstrap-icons/icons/people.svg";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
|
||||
import { ReactComponent as Logo } from "../assets/brick.svg";
|
||||
import styles from "./Header.module.css";
|
||||
|
||||
const ADMIN_URL = import.meta.env.VITE_API_URL
|
||||
? `${import.meta.env.VITE_API_URL}/admin`
|
||||
: "http://localhost:8000/admin/";
|
||||
const ADMIN_URL = `${import.meta.env.VITE_API_URL}/admin/`;
|
||||
|
||||
const navLinkStyles = ({ isActive }: { isActive: boolean }) => (isActive ? "nav-link active" : "nav-link");
|
||||
const navLinkStyles = ({ isActive }: { isActive: boolean }) => clsx(styles.navLink, { [styles.active]: isActive });
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
export const Header = (): JSX.Element => {
|
||||
return (
|
||||
<header>
|
||||
<nav className="navbar navbar-dark bg-dark navbar-expand-md">
|
||||
<NavLink to="/" className="navbar-brand">
|
||||
<span className={styles.logo}>
|
||||
<Logo />
|
||||
</span>
|
||||
Biceps Bar System - BBS
|
||||
<header className={styles.header}>
|
||||
<nav className={styles.nav}>
|
||||
<NavLink to="/" className={styles.logoNav}>
|
||||
<div className={styles.brand}>BBS</div>
|
||||
<div className={styles.subtitle}>Biceps Bar System</div>
|
||||
</NavLink>
|
||||
<button
|
||||
className="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span className="navbar-toggler-icon" />
|
||||
</button>
|
||||
<div className="collapse navbar-collapse" id="navbarNav">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item">
|
||||
<ul className={styles.navItems}>
|
||||
<li>
|
||||
<NavLink to="/" className={navLinkStyles} end>
|
||||
<span className={styles.navIcon}>
|
||||
<House />
|
||||
</span>{" "}
|
||||
Kjøp drikke
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<li>
|
||||
<NavLink to="/deposit" className={navLinkStyles}>
|
||||
Sett inn penger
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<li>
|
||||
<NavLink to="/log" className={navLinkStyles}>
|
||||
<span className={styles.navIcon}>
|
||||
<CardList />
|
||||
</span>{" "}
|
||||
Logg
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<li>
|
||||
<NavLink to="/stats" className={navLinkStyles}>
|
||||
<span className={styles.navIcon}>
|
||||
<BarChart />
|
||||
</span>{" "}
|
||||
Statistikk
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li className="nav-item">
|
||||
<li>
|
||||
<NavLink to="/users" className={navLinkStyles}>
|
||||
<span className={styles.navIcon}>
|
||||
<PeopleIcon />
|
||||
</span>{" "}
|
||||
Folk
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item">
|
||||
<a href={ADMIN_URL} className="nav-link">
|
||||
<li>
|
||||
<a href={ADMIN_URL} className={styles.navLink}>
|
||||
Admin
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
.product {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 4px solid transparent;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
transition: background-color 0.1s ease-out, border 0.1s ease-out;
|
||||
}
|
||||
.product:hover {
|
||||
border-color: rgba(25, 135, 84, 0.6);
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.active {
|
||||
border-color: rgba(25, 135, 84, 0.6);
|
||||
background: rgba(25, 135, 84, 0.2);
|
||||
}
|
||||
.active:hover {
|
||||
background: rgba(25, 135, 84, 0.2);
|
||||
}
|
||||
.productText {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 4px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
.productImage {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.noProductImage {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
.amount {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
padding: 4px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
.wrap {
|
||||
position: relative;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import clsx from "clsx";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
import { Product } from "../types";
|
||||
import styles from "./ProductCard.module.css";
|
||||
|
||||
type ProductCardProps = {
|
||||
product: Omit<Product, "userCounts">;
|
||||
onProduct: MouseEventHandler;
|
||||
active: boolean;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export const ProductCard: React.FC<ProductCardProps> = ({ product, onProduct, amount, active = false }) => {
|
||||
const classes = clsx(styles.product, { [styles.active]: active });
|
||||
return (
|
||||
<button className={classes} type="button" onClick={onProduct}>
|
||||
<div className={styles.wrap}>
|
||||
<span className={styles.amount}>{amount}</span>
|
||||
{product.imageUrl && <img src={product.imageUrl} className={styles.productImage} alt={product.name} />}
|
||||
{!product.imageUrl && <div className={styles.noProductImage} />}
|
||||
<span className={styles.productText}>{product.name}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
.splash {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.overlay {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sun {
|
||||
width: clamp(100px, 65vw, 600px);
|
||||
height: clamp(100px, 65vw, 600px);
|
||||
margin: 0 auto;
|
||||
background: linear-gradient(var(--sun) 50%, var(--sun-end) 90%);
|
||||
border-radius: 50% 50%;
|
||||
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun), 0 0 30px var(--sun);
|
||||
}
|
||||
.brand {
|
||||
position: relative;
|
||||
left: -0.6rem;
|
||||
padding-top: 3rem;
|
||||
color: var(--brand);
|
||||
font-family: Rocket Rinder, monospace;
|
||||
font-size: clamp(20px, 20vw, 200px);
|
||||
font-style: italic;
|
||||
letter-spacing: 1rem;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
position: relative;
|
||||
bottom: 0.5rem;
|
||||
left: 0rem;
|
||||
animation: flicker 1.5s infinite alternate;
|
||||
color: var(--interlace);
|
||||
font-family: Rocket Clouds DEMO, serif;
|
||||
font-size: clamp(20px, 5vw, 50px);
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
transform: rotate(-6deg);
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0%,
|
||||
18%,
|
||||
22%,
|
||||
25%,
|
||||
53%,
|
||||
57%,
|
||||
100% {
|
||||
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8);
|
||||
}
|
||||
|
||||
20%,
|
||||
24%,
|
||||
55% {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import styles from "./Splash.module.css";
|
||||
|
||||
interface SplashProps {
|
||||
brandText?: string;
|
||||
noOverlay?: boolean;
|
||||
}
|
||||
|
||||
const Splash = ({ brandText = "BBS", noOverlay = false }: SplashProps): JSX.Element => (
|
||||
<div className={clsx(styles.splash, { [styles.overlay]: !noOverlay })}>
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.sun}></div>
|
||||
<div className={styles.brand}>{brandText}</div>
|
||||
<div className={styles.subtitle}>Biceps Bar System</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default Splash;
|
||||
@@ -2,4 +2,27 @@
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
left: 32px;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.visible {
|
||||
opacity: 1;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
||||
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||
}
|
||||
.danger {
|
||||
background: var(--brand);
|
||||
box-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8);
|
||||
}
|
||||
|
||||
.close {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,41 +1,18 @@
|
||||
import { Toast as BSToast } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React from "react";
|
||||
|
||||
import styles from "./Toast.module.css";
|
||||
import { useToast } from "./ToastProvider";
|
||||
|
||||
export const Toast: React.FC = () => {
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const { visible, showToast, message, background } = useToast();
|
||||
export const Toast = (): JSX.Element => {
|
||||
const { showToast, message, background, visible } = useToast();
|
||||
|
||||
const backgroundColor = `bg-${background}`;
|
||||
const classes = clsx("toast align-items-center text-white bg-primary border-0", backgroundColor, styles.toast);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const bsToast = BSToast.getInstance(ref.current) || new BSToast(ref.current);
|
||||
|
||||
if (visible) {
|
||||
bsToast?.show();
|
||||
} else if (!visible) {
|
||||
bsToast?.hide();
|
||||
}
|
||||
return () => bsToast?.hide();
|
||||
}, [visible]);
|
||||
const classes = clsx([styles[background], styles.toast], { [styles.visible]: visible });
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classes} role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div className="d-flex">
|
||||
<div className="toast-body">{message}</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close btn-close-white me-2 m-auto"
|
||||
data-bs-dismiss="toast"
|
||||
aria-label="Close"
|
||||
onClick={() => showToast(message)}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes} role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div>{message}</div>
|
||||
<button type="button" aria-label="Close" onClick={() => showToast(message)} className={styles.close} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,21 +1,24 @@
|
||||
import React, { ReactNode, useContext, useState } from "react";
|
||||
|
||||
type Background = "primary" | "danger";
|
||||
|
||||
interface ToastProps {
|
||||
visible: boolean;
|
||||
message: string;
|
||||
showToast: (message: string, background?: string) => void;
|
||||
showToast: (message: string, background?: Background) => void;
|
||||
hide: () => void;
|
||||
background: string;
|
||||
background: Background;
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
// FIXME: revert
|
||||
visible: false,
|
||||
message: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
showToast: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
hide: () => {},
|
||||
background: "primary",
|
||||
background: "primary" as Background,
|
||||
};
|
||||
|
||||
const ToastContext = React.createContext<ToastProps>(defaultState);
|
||||
@@ -23,19 +26,18 @@ const ToastContext = React.createContext<ToastProps>(defaultState);
|
||||
type ToastProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
|
||||
export const ToastProvider = ({ children }: ToastProviderProps): JSX.Element => {
|
||||
const [visible, setVisible] = useState(defaultState.visible);
|
||||
const [message, setMessage] = useState("");
|
||||
const [background, setBackground] = useState("");
|
||||
const [message, setMessage] = useState(defaultState.message);
|
||||
const [background, setBackground] = useState(defaultState.background);
|
||||
|
||||
const showToast = (newMessage: string, background = "primary") => {
|
||||
const showToast = (newMessage: string, background: Background = "primary") => {
|
||||
setMessage(newMessage);
|
||||
setBackground(background);
|
||||
if (!visible) {
|
||||
setVisible(true);
|
||||
setTimeout(() => {
|
||||
setVisible(false);
|
||||
setMessage("");
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
.user {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
border: 3px solid transparent;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
transition: background-color 0.1s ease-out, border 0.1s ease-out;
|
||||
}
|
||||
.user:hover {
|
||||
border-color: rgba(25, 135, 84, 0.6);
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.active {
|
||||
border-color: rgba(25, 135, 84, 0.6);
|
||||
background: rgba(25, 135, 84, 0.2);
|
||||
}
|
||||
.active:hover {
|
||||
background: rgba(25, 135, 84, 0.2);
|
||||
}
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50px;
|
||||
}
|
||||
.avatar svg {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
color: #ccc;
|
||||
}
|
||||
.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { ReactComponent as PersonFill } from "bootstrap-icons/icons/person-fill.svg";
|
||||
import clsx from "clsx";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
import { AllUsersQuery } from "../types";
|
||||
import styles from "./UserCard.module.css";
|
||||
|
||||
type UserCardProps = {
|
||||
user: AllUsersQuery["allUsers"][0] | null;
|
||||
onUser: MouseEventHandler;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
/** If user is null, then it's the special external user */
|
||||
export const UserCard: React.FC<UserCardProps> = ({ user, onUser, active = false }) => {
|
||||
const classes = clsx(styles.user, { [styles.active]: active });
|
||||
return (
|
||||
<button className={classes} onClick={onUser} type="button">
|
||||
{user?.profile?.image && (
|
||||
<img src={user.profile?.image} alt={`Profile for ${user.firstName}`} className={styles.avatar} />
|
||||
)}
|
||||
{!user?.profile?.image && (
|
||||
<span className={styles.avatar}>
|
||||
<PersonFill />
|
||||
</span>
|
||||
)}
|
||||
<span className={styles.name}>{user?.firstName || "Ekstern"}</span>
|
||||
<span className={styles.balance}>{user?.profile?.balance || ""}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
.wireframe {
|
||||
position: absolute;
|
||||
bottom: calc(22% - 512px);
|
||||
left: calc(50% - 512px);
|
||||
width: 1024px;
|
||||
height: 1024px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 128px);
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: isometric-rotate;
|
||||
animation-duration: 10s;
|
||||
animation-timing-function: linear;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.cell {
|
||||
border-bottom: 2px solid var(--sun);
|
||||
border-right: 2px solid var(--sun);
|
||||
|
||||
}
|
||||
|
||||
.cell:nth-child(8n) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.cell:nth-child(n+57) {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@keyframes isometric-rotate {
|
||||
0% {
|
||||
transform: rotateX(70deg) rotateY(0deg) rotateZ(-45deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(70deg) rotateY(0deg) rotateZ(-405deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
import styles from "./Wireframe.module.css";
|
||||
|
||||
export const Wireframe = () => (
|
||||
<div className={styles.wireframe}>
|
||||
{[...Array(64)].map((_, idx) => (
|
||||
<div key={idx} className={styles.cell} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
.form {
|
||||
display: grid;
|
||||
max-width: 500px;
|
||||
gap: 1rem;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
import { CreateUserDocument } from "../../types";
|
||||
import styles from "./UserCreate.module.css";
|
||||
|
||||
type FormValues = {
|
||||
firstName: string;
|
||||
@@ -10,7 +11,7 @@ type FormValues = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export const UserCreate = () => {
|
||||
const UserCreate = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@@ -30,20 +31,21 @@ export const UserCreate = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<h2>Opprett ny</h2>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
|
||||
<label>Fornavn</label>
|
||||
<input {...register("firstName", { required: true })} />
|
||||
{errors.firstName && <span>This field is required</span>}
|
||||
<br />
|
||||
<label>Etternavn</label>
|
||||
<input {...register("lastName", { required: true })} />
|
||||
{errors.lastName && <span>This field is required</span>}
|
||||
<br />
|
||||
<label>E-post</label>
|
||||
<input type="email" {...register("email", { required: true })} />
|
||||
{errors.email && <span>This field is required</span>}
|
||||
<br />
|
||||
<input type="submit" disabled={creating} />
|
||||
<input type="submit" disabled={creating} className="btn-primary" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default UserCreate;
|
||||
@@ -1,13 +1,17 @@
|
||||
.userList {
|
||||
display: grid;
|
||||
height: 400px;
|
||||
grid-template-columns: repeat(12, 100px);
|
||||
height: 800px;
|
||||
padding-top: 2rem;
|
||||
grid-template-columns: repeat(5, 220px);
|
||||
overflow-y: auto;
|
||||
row-gap: 16px;
|
||||
column-gap: 16px;
|
||||
row-gap: 24px;
|
||||
}
|
||||
|
||||
.amountSection {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.checkout {
|
||||
margin-top: 32px;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
|
||||
import { UserCard } from "../../components/UserCard";
|
||||
import { UserCard } from "../users/UserCard";
|
||||
import styles from "./Deposit.module.css";
|
||||
import { useDeposit } from "./useDeposit";
|
||||
|
||||
export const Deposit: React.FC = () => {
|
||||
const Deposit = (): JSX.Element => {
|
||||
const {
|
||||
loading,
|
||||
selectedUser,
|
||||
@@ -33,7 +33,7 @@ export const Deposit: React.FC = () => {
|
||||
placeholder="Filtrér på navn"
|
||||
/>
|
||||
{userQuery !== "" && (
|
||||
<button className="btn btn-link" type="button" onClick={() => setUserQuery("")}>
|
||||
<button type="button" onClick={() => setUserQuery("")}>
|
||||
Vis alle
|
||||
</button>
|
||||
)}
|
||||
@@ -55,13 +55,14 @@ export const Deposit: React.FC = () => {
|
||||
<input name="amount" value={amount} onChange={(event) => setAmount(event.target.value)} placeholder="0" />
|
||||
</section>
|
||||
<section className={styles.checkout}>
|
||||
<button type="button" className="btn btn-primary btn-lg" onClick={onSubmit}>
|
||||
<button type="button" onClick={onSubmit} className="primary">
|
||||
Sett inn
|
||||
</button>{" "}
|
||||
<button type="button" className="btn btn-outline-secondary btn-lg" onClick={reset}>
|
||||
<button type="button" onClick={reset}>
|
||||
Start på nytt
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Deposit;
|
||||
@@ -3,3 +3,30 @@
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.disabled,
|
||||
.disabled a:link {
|
||||
color: rgba(121, 152, 238, 0.5);
|
||||
}
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: 1.5rem;
|
||||
gap: 0 1rem;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.pagination a {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border: 1px solid var(--border);
|
||||
margin-top: 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.pagination a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.disabled a:hover {
|
||||
color: rgba(121, 152, 238, 0.5);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import styles from "./Log.module.css";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export const Log = () => {
|
||||
const Log = () => {
|
||||
const types = {
|
||||
orderOffset: Number,
|
||||
transactionOffset: Number,
|
||||
@@ -34,7 +34,7 @@ export const Log = () => {
|
||||
<div className={styles.log}>
|
||||
<div>
|
||||
<h2>Ordre</h2>
|
||||
<table className="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Når</th>
|
||||
@@ -61,29 +61,27 @@ export const Log = () => {
|
||||
</tbody>
|
||||
</table>
|
||||
<nav aria-label="Order pagination">
|
||||
<ul className="pagination">
|
||||
<li className={clsx("page-item", { disabled: orderOffset <= 0 })}>
|
||||
<ul className={styles.pagination}>
|
||||
<li className={clsx({ [styles.disabled]: orderOffset <= 0 })}>
|
||||
<a
|
||||
href="#"
|
||||
className="page-link"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setParams({ orderOffset: orderOffset - PAGE_SIZE });
|
||||
setParams({ orderOffset: Math.max(0, orderOffset - PAGE_SIZE) });
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
⬅
|
||||
</a>
|
||||
</li>
|
||||
<li className="page-item">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
className="page-link"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setParams({ orderOffset: orderOffset + PAGE_SIZE });
|
||||
setParams({ orderOffset: Math.max(0, orderOffset + PAGE_SIZE) });
|
||||
}}
|
||||
>
|
||||
Next
|
||||
➡
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -91,7 +89,7 @@ export const Log = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h2>Transaksjoner</h2>
|
||||
<table className="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Når</th>
|
||||
@@ -112,29 +110,27 @@ export const Log = () => {
|
||||
</tbody>
|
||||
</table>
|
||||
<nav aria-label="Transaction pagination">
|
||||
<ul className="pagination">
|
||||
<li className={clsx("page-item", { disabled: transactionOffset <= 0 })}>
|
||||
<ul className={styles.pagination}>
|
||||
<li className={clsx({ [styles.disabled]: transactionOffset <= 0 })}>
|
||||
<a
|
||||
href="#"
|
||||
className="page-link"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setParams({ transactionOffset: transactionOffset - PAGE_SIZE });
|
||||
setParams({ transactionOffset: Math.max(0, transactionOffset - PAGE_SIZE) });
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
⬅
|
||||
</a>
|
||||
</li>
|
||||
<li className="page-item">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
className="page-link"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setParams({ transactionOffset: transactionOffset + PAGE_SIZE });
|
||||
setParams({ transactionOffset: Math.max(transactionOffset + PAGE_SIZE) });
|
||||
}}
|
||||
>
|
||||
Next
|
||||
➡
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -143,3 +139,4 @@ export const Log = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Log;
|
||||
@@ -0,0 +1,51 @@
|
||||
.product {
|
||||
display: flex;
|
||||
border: none;
|
||||
background: rgb(35, 15, 41);
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.1s ease-out, border 0.1s ease-out;
|
||||
position: relative;
|
||||
align-items: flex-start;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.product:hover,
|
||||
.active.product {
|
||||
background-color: var(--background);
|
||||
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background);
|
||||
}
|
||||
|
||||
.productText {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 1.2rem;
|
||||
text-shadow: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.amount {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 12px 12px 8px 8px;
|
||||
border-left: 1px solid transparent;
|
||||
color: transparent;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
background-color: transparent;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active .amount {
|
||||
display: block;
|
||||
color: var(--background);
|
||||
text-shadow: none;
|
||||
border-left-color: rgb(35, 15, 41);
|
||||
background-color: var(--sun-end);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import clsx from "clsx";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
import { Product } from "../../types";
|
||||
import styles from "./ProductCard.module.css";
|
||||
|
||||
type ProductCardProps = {
|
||||
product: Omit<Product, "userCounts">;
|
||||
onProduct: MouseEventHandler;
|
||||
active: boolean;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export const ProductCard = ({ product, onProduct, amount, active = false }: ProductCardProps): JSX.Element => {
|
||||
const classes = clsx(styles.product, { [styles.active]: active });
|
||||
return (
|
||||
<button className={classes} type="button" onClick={onProduct} title={product.name}>
|
||||
<span className={styles.amount}>{amount}</span>
|
||||
<span className={styles.productText}>{product.name}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,21 @@
|
||||
.userList {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 128px);
|
||||
row-gap: 16px;
|
||||
grid-template-columns: repeat(5, 220px);
|
||||
column-gap: 16px;
|
||||
row-gap: 24px;
|
||||
}
|
||||
|
||||
.showAll {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.productList {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 150px);
|
||||
row-gap: 16px;
|
||||
grid-template-columns: repeat(5, 220px);
|
||||
column-gap: 16px;
|
||||
row-gap: 24px;
|
||||
}
|
||||
|
||||
.productListSection {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
@@ -22,7 +29,6 @@
|
||||
min-width: 200px;
|
||||
padding: 4px 8px;
|
||||
margin-right: 8px;
|
||||
background: black;
|
||||
color: white;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import { ProductCard } from "../../components/ProductCard";
|
||||
import { UserCard } from "../../components/UserCard";
|
||||
import { OrderLineInput, Product } from "../../types";
|
||||
import { UserCard } from "../users/UserCard";
|
||||
import { ProductCard } from "./ProductCard";
|
||||
import styles from "./Register.module.css";
|
||||
import { useRegister } from "./useRegister";
|
||||
|
||||
const USER_EXTERNAL = "external";
|
||||
|
||||
export const Register: React.FC = () => {
|
||||
const Register = (): JSX.Element => {
|
||||
const {
|
||||
users,
|
||||
products,
|
||||
@@ -48,7 +48,7 @@ export const Register: React.FC = () => {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<button type="button" className="btn btn-link" onClick={() => setShowAll(!showAll)}>
|
||||
<button type="button" onClick={() => setShowAll(!showAll)} className={styles.showAll}>
|
||||
{showAll ? "Skjul gamliser" : "Vis alle"}
|
||||
</button>
|
||||
</section>
|
||||
@@ -70,13 +70,14 @@ export const Register: React.FC = () => {
|
||||
<span className={styles.total}>
|
||||
Totalt: <span>{total}</span>
|
||||
</span>
|
||||
<button type="button" className="btn btn-primary btn-lg" onClick={onSubmit}>
|
||||
<button type="button" onClick={onSubmit} className="primary">
|
||||
Kjøp
|
||||
</button>{" "}
|
||||
<button type="button" className="btn btn-outline-secondary btn-lg" onClick={reset}>
|
||||
<button type="button" onClick={reset}>
|
||||
Start på nytt
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Register;
|
||||
@@ -35,7 +35,7 @@ const OrderBar = ({ data }: { data: BarDatum[] }) => (
|
||||
/>
|
||||
);
|
||||
|
||||
export const OrderStats = () => {
|
||||
const OrderStats = () => {
|
||||
const { data, loading } = useQuery(OrderStatsDocument);
|
||||
|
||||
return (
|
||||
@@ -77,3 +77,4 @@ export const OrderStats = () => {
|
||||
</StatsView>
|
||||
);
|
||||
};
|
||||
export default OrderStats;
|
||||
@@ -11,7 +11,7 @@ type UserCount = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
export const ProductStats = () => {
|
||||
const ProductStats = () => {
|
||||
const { data, loading } = useQuery(AllProductStatsDocument, {
|
||||
variables: { active: true },
|
||||
});
|
||||
@@ -38,3 +38,4 @@ export const ProductStats = () => {
|
||||
</StatsView>
|
||||
);
|
||||
};
|
||||
export default ProductStats;
|
||||
@@ -4,6 +4,26 @@
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.statsNav {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.statsNav ul {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
gap: 0 1rem;
|
||||
list-style: none;
|
||||
}
|
||||
.statsNav a {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.active.active {
|
||||
color: white;
|
||||
}
|
||||
.statsNav a:hover {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import React from "react";
|
||||
import { ProductStatsDocument } from "../../types";
|
||||
import { StatsView } from "./StatsView";
|
||||
|
||||
export const Stats = () => {
|
||||
const Stats = () => {
|
||||
const { data, loading } = useQuery(ProductStatsDocument);
|
||||
|
||||
const series: any[] = [];
|
||||
@@ -68,3 +68,4 @@ export const Stats = () => {
|
||||
</StatsView>
|
||||
);
|
||||
};
|
||||
export default Stats;
|
||||
@@ -1,34 +1,36 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
|
||||
import styles from "./Stats.module.css";
|
||||
const navLinkStyles = ({ isActive }: { isActive: boolean }) => (isActive ? "nav-link active" : "nav-link");
|
||||
|
||||
const navLinkStyles = ({ isActive }: { isActive: boolean }) => clsx(styles.navLink, { [styles.active]: isActive });
|
||||
|
||||
export const StatsNav = () => (
|
||||
<div className={styles.statsNav}>
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item">
|
||||
<nav className={styles.statsNav}>
|
||||
<ul>
|
||||
<li>
|
||||
<NavLink className={navLinkStyles} to="/stats" end>
|
||||
Oversikt
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<li>
|
||||
<NavLink className={navLinkStyles} to="/stats/products">
|
||||
Produkter
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<li>
|
||||
<NavLink className={navLinkStyles} to="/stats/orders">
|
||||
Ordre
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink className="nav-link disabled" to="/stats/users" title="TODO">
|
||||
<li>
|
||||
<NavLink className="disabled" to="/stats/users" title="TODO">
|
||||
Folk
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
|
||||
export const StatsView = ({ children, loading }: { children: React.ReactNode; loading: boolean }) => (
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-left: 4px solid transparent;
|
||||
background: rgb(35, 15, 41);
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
transition: background-color 0.1s ease-out, border 0.1s ease-out;
|
||||
max-height: 82px;
|
||||
}
|
||||
|
||||
.user:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.user:hover,
|
||||
.active.user {
|
||||
background-color: var(--background);
|
||||
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background);
|
||||
}
|
||||
.active.user {
|
||||
border-left: 4px solid var(--sun)
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-left: 1rem;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
|
||||
}
|
||||
.avatarWrap {
|
||||
filter: drop-shadow(0 0 4px var(--background)) drop-shadow(0 0 11px var(--background))
|
||||
drop-shadow(0 0 19px var(--background));
|
||||
}
|
||||
|
||||
.noAvatar {
|
||||
margin-left: 1rem;
|
||||
display: block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: black;
|
||||
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 1.2rem;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.balance {
|
||||
color: var(--sun)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import clsx from "clsx";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
import { AllUsersQuery } from "../../types";
|
||||
import styles from "./UserCard.module.css";
|
||||
|
||||
type UserCardProps = {
|
||||
user: AllUsersQuery["allUsers"][0] | null;
|
||||
onUser: MouseEventHandler;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
/** If user is null, then it's the special external user */
|
||||
export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.Element => {
|
||||
const classes = clsx(styles.user, { [styles.active]: active });
|
||||
return (
|
||||
<button className={classes} onClick={onUser} type="button" title={user?.firstName || "Ekstern"}>
|
||||
<div className={styles.avatarWrap}>
|
||||
{user?.profile?.image && (
|
||||
<img src={user.profile?.image} alt={`Profile for ${user.firstName}`} className={styles.avatar} />
|
||||
)}
|
||||
{!user?.profile?.image && (
|
||||
<span className={styles.avatar}>
|
||||
<span className={styles.noAvatar} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.meta}>
|
||||
{user?.firstName || "Ekstern"}
|
||||
<span className={styles.balance}>{user?.profile?.balance || ""}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -3,16 +3,19 @@ import { Line } from "@nivo/line";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { User, UserDetailDocument, UserProfile } from "../../types";
|
||||
import { formatMonth, formatTime } from "../../utils/time";
|
||||
import styles from "./UserDetail.module.css";
|
||||
|
||||
export const UserDetail = () => {
|
||||
const UserDetail = () => {
|
||||
const { userId } = useParams();
|
||||
const { data, loading } = useQuery(UserDetailDocument, { variables: { userId: userId || "" }, skip: !userId });
|
||||
const { data, loading, error } = useQuery(UserDetailDocument, { variables: { userId: userId || "" }, skip: !userId });
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
|
||||
if (error) return <GQLError error={error} />;
|
||||
|
||||
const user = data?.user as User;
|
||||
const profile = user?.profile as UserProfile;
|
||||
// FIXME: types
|
||||
@@ -30,26 +33,26 @@ export const UserDetail = () => {
|
||||
<h1>
|
||||
{profile.image && <img src={profile.image} alt={`${user.firstName}s profile picture`} />} {user.firstName}
|
||||
</h1>
|
||||
<div className="card">
|
||||
<div className="card-header">Stats</div>
|
||||
<ul className="list-group list-group-flush">
|
||||
<li className="list-group-item">
|
||||
<div>
|
||||
<div>Stats</div>
|
||||
<ul>
|
||||
<li>
|
||||
Saldo: <strong>{profile.balance.toLocaleString("no")} kr</strong>
|
||||
</li>
|
||||
<li className="list-group-item">
|
||||
<li>
|
||||
Siste kjøp: <strong>{formatTime(profile.lastPurchaseDate)}</strong>
|
||||
</li>
|
||||
<li className="list-group-item">
|
||||
<li>
|
||||
Kjøp totalt: <strong>{profile.orderSumTotal.toLocaleString("no")} kr</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br />
|
||||
<div className="card">
|
||||
<div className="card-header">Top 5 måneder</div>
|
||||
<ul className="list-group list-group-flush">
|
||||
<div>
|
||||
<div>Top 5 måneder</div>
|
||||
<ul>
|
||||
{profile.topMonths.map((topMonth) => (
|
||||
<li key={topMonth.period} className="list-group-item">
|
||||
<li key={topMonth.period}>
|
||||
{formatMonth(topMonth.period)}: <strong>{topMonth.count}</strong> kjøp på totalt{" "}
|
||||
<strong>{topMonth.sum} kr</strong>
|
||||
</li>
|
||||
@@ -108,3 +111,4 @@ export const UserDetail = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default UserDetail;
|
||||
@@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
|
||||
import { AllUsersDocument } from "../../types";
|
||||
import { activeUserFilter } from "../../utils/users";
|
||||
|
||||
export const UserList = () => {
|
||||
const UserList = () => {
|
||||
const { data, loading } = useQuery(AllUsersDocument);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
|
||||
@@ -15,7 +15,8 @@ export const UserList = () => {
|
||||
const users = showAll ? allUsers : allUsers.filter((u) => activeUserFilter(u));
|
||||
return (
|
||||
<div>
|
||||
<button type="button" className="btn btn-link" onClick={() => setShowAll(!showAll)}>
|
||||
<h2>Folk</h2>
|
||||
<button type="button" onClick={() => setShowAll(!showAll)}>
|
||||
{showAll ? "Skjul gamliser" : "Vis alle"}
|
||||
</button>
|
||||
<ul>
|
||||
@@ -28,9 +29,10 @@ export const UserList = () => {
|
||||
))}
|
||||
</ul>
|
||||
<br />
|
||||
<Link to="/users/create" className="btn btn-primary">
|
||||
<Link to="/users/create" className="btn-primary">
|
||||
Opprett ny
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default UserList;
|
||||
@@ -0,0 +1,145 @@
|
||||
/* Custom props */
|
||||
:root {
|
||||
--brand: rgb(243, 33, 61);
|
||||
--background: rgb(70, 30, 82);
|
||||
--text: rgb(255, 255, 255);
|
||||
--sun: rgb(221, 81, 127);
|
||||
--sun-end: rgb(230, 142, 54);
|
||||
--border: rgb(85, 109, 200);
|
||||
--interlace: rgb(121, 152, 238);
|
||||
--font-family: "IBM Plex Mono", monospace;
|
||||
}
|
||||
|
||||
/* Resets */
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
/* Body and interitable styles */
|
||||
body {
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.8) 60%, var(--background) 100%);
|
||||
background-color: var(--background);
|
||||
color: var(--text);
|
||||
font-family: var(--font-family);
|
||||
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a:link {
|
||||
color: var(--interlace);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--interlace);
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: var(--interlace);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
color: var(--sun);
|
||||
}
|
||||
a.btn-primary {
|
||||
padding: 0.4rem 1rem;
|
||||
border: 1px solid transparent;
|
||||
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
padding: 0.4rem 1rem;
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
color: var(--border);
|
||||
font-family: var(--font-family);
|
||||
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
|
||||
}
|
||||
button:hover {
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
button.primary {
|
||||
border: 1px solid transparent;
|
||||
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
||||
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
input {
|
||||
padding: 0.4rem 0.4rem;
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
font-family: var(--font-family);
|
||||
font-size: 1.5rem;
|
||||
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
|
||||
}
|
||||
input:focus {
|
||||
background: rgba(85, 109, 200, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
input.btn-primary {
|
||||
padding: 0.4rem 1rem;
|
||||
border: 1px solid transparent;
|
||||
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||
color: var(--text);
|
||||
}
|
||||
::placeholder {
|
||||
color: var(--border);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
border: 1px solid var(--border);
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
padding: 0.5rem 0.8rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* Fonts*/
|
||||
@font-face {
|
||||
font-family: "Rocket Clouds DEMO";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("assets/rocket-clouds.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Rocket Rinder";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("assets/rocket-rinder.ttf") format("truetype");
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import ReactDOM from "react-dom";
|
||||
|
||||
import App from "./App";
|
||||
|
||||
import "./global.css";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
|
||||
@@ -4,11 +4,10 @@ import svgr from "vite-plugin-svgr";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: process.env.NODE_ENV !== "production" ? "/" : "/static/modern/",
|
||||
plugins: [svgr()],
|
||||
define: {
|
||||
__DEV__: process.env.NODE_ENV !== "production",
|
||||
},
|
||||
envDir: "..",
|
||||
build: { outDir: "../static/modern", emptyOutDir: true },
|
||||
build: { outDir: "../static", emptyOutDir: false },
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from main.api.types.inventory import Transaction, TransactionOrdering
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class InventoryQueries:
|
||||
transaction_list: list[Transaction] = strawberry_django.field(pagination=True, order=TransactionOrdering)
|
||||
@@ -1,20 +0,0 @@
|
||||
import strawberry
|
||||
|
||||
from main.api.mutations.shop import ShopMutations
|
||||
from main.api.mutations.users import UsersMutations
|
||||
from main.api.queries.inventory import InventoryQueries
|
||||
from main.api.queries.shop import ShopQueries
|
||||
from main.api.queries.users import UsersQueries
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class Query(InventoryQueries, ShopQueries, UsersQueries):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class Mutation(ShopMutations, UsersMutations):
|
||||
pass
|
||||
|
||||
|
||||
schema = strawberry.Schema(query=Query, mutation=Mutation)
|
||||
@@ -1,11 +0,0 @@
|
||||
from django.urls import re_path
|
||||
|
||||
from main.views.reports import inventory, inventory_add, report
|
||||
|
||||
urlpatterns = [
|
||||
# TODO: migrate to SPA
|
||||
# Reports
|
||||
re_path(r"^inventory/$", inventory, name="inventory"),
|
||||
re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
|
||||
re_path(r"^report$", report, name="report"),
|
||||
]
|
||||
Whitespace-only changes.
@@ -3,7 +3,7 @@ import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbssite.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbs.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
|
||||
Generated
+294
-49
@@ -31,6 +31,25 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
|
||||
|
||||
[[package]]
|
||||
name = "backports.cached-property"
|
||||
version = "1.0.1"
|
||||
description = "cached_property() - computed once per instance, cached as attribute"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "backports.zoneinfo"
|
||||
version = "0.2.1"
|
||||
description = "Backport of the standard library zoneinfo module"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
tzdata = ["tzdata"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.1.0"
|
||||
@@ -53,14 +72,6 @@ d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "cached-property"
|
||||
version = "1.5.2"
|
||||
description = "A decorator for caching properties in classes."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2021.10.8"
|
||||
@@ -96,6 +107,22 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "cssselect2"
|
||||
version = "0.4.1"
|
||||
description = "cssselect2"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
tinycss2 = "*"
|
||||
webencodings = "*"
|
||||
|
||||
[package.extras]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.3.4"
|
||||
@@ -106,16 +133,17 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "3.2.12"
|
||||
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
|
||||
version = "4.0.2"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.8"
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.3.2,<4"
|
||||
pytz = "*"
|
||||
asgiref = ">=3.4.1,<4"
|
||||
"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""}
|
||||
sqlparse = ">=0.2.2"
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||
@@ -143,17 +171,30 @@ python-versions = ">=3.6"
|
||||
[package.dependencies]
|
||||
Django = ">=2.2"
|
||||
|
||||
[[package]]
|
||||
name = "django-rename-app"
|
||||
version = "0.1.3"
|
||||
description = "A Django Management Command to rename existing Django Applications."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
django = "*"
|
||||
|
||||
[[package]]
|
||||
name = "easy-thumbnails"
|
||||
version = "2.7.2"
|
||||
version = "2.8.1"
|
||||
description = "Easy thumbnails for Django"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=1.11,<4.0"
|
||||
django = ">=2.2"
|
||||
pillow = "*"
|
||||
reportlab = "*"
|
||||
svglib = "*"
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
@@ -222,6 +263,20 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
plugins = ["setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "4.8.0"
|
||||
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
|
||||
|
||||
[package.extras]
|
||||
cssselect = ["cssselect (>=0.7)"]
|
||||
html5 = ["html5lib"]
|
||||
htmlsoup = ["beautifulsoup4"]
|
||||
source = ["Cython (>=0.29.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
@@ -275,7 +330,7 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -398,14 +453,6 @@ python-versions = "*"
|
||||
[package.dependencies]
|
||||
six = ">=1.4.0"
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2021.3"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0"
|
||||
@@ -414,6 +461,20 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "reportlab"
|
||||
version = "3.6.7"
|
||||
description = "The Reportlab Toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6, <4"
|
||||
|
||||
[package.dependencies]
|
||||
pillow = ">=4.0.0"
|
||||
|
||||
[package.extras]
|
||||
rlpycairo = ["rlPyCairo (>=0.0.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "sentinel"
|
||||
version = "0.3.0"
|
||||
@@ -427,7 +488,7 @@ varname = ["varname (>=0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "1.4.3"
|
||||
version = "1.5.5"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -449,6 +510,7 @@ flask = ["flask (>=0.11)", "blinker (>=1.1)"]
|
||||
httpx = ["httpx (>=0.16.0)"]
|
||||
pure_eval = ["pure-eval", "executing", "asttokens"]
|
||||
pyspark = ["pyspark (>=2.4.4)"]
|
||||
quart = ["quart (>=0.16.1)", "blinker (>=1.1)"]
|
||||
rq = ["rq (>=0.6)"]
|
||||
sanic = ["sanic (>=0.8)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.2)"]
|
||||
@@ -472,14 +534,14 @@ python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "strawberry-graphql"
|
||||
version = "0.93.23"
|
||||
version = "0.97.0"
|
||||
description = "A library for creating GraphQL APIs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7,<4.0"
|
||||
|
||||
[package.dependencies]
|
||||
cached-property = ">=1.5.2,<2.0.0"
|
||||
"backports.cached-property" = ">=1.0.1,<2.0.0"
|
||||
click = ">=7.0,<9.0"
|
||||
graphql-core = ">=3.1.0,<3.2.0"
|
||||
pygments = ">=2.3,<3.0"
|
||||
@@ -490,7 +552,7 @@ typing_extensions = ">=3.7.4,<5.0.0"
|
||||
|
||||
[package.extras]
|
||||
asgi = ["starlette (>=0.13.6,<0.17.0)"]
|
||||
debug-server = ["starlette (>=0.13.6,<0.17.0)", "uvicorn (>=0.11.6,<0.17.0)"]
|
||||
debug-server = ["starlette (>=0.13.6,<0.17.0)", "uvicorn (>=0.11.6,<0.18.0)"]
|
||||
django = ["Django (>=2.2,<4)", "Django (>=2.2,<5)", "asgiref (>=3.2,<4.0)"]
|
||||
flask = ["flask (>=1.1,<2.0)"]
|
||||
opentelemetry = ["opentelemetry-api (<2)", "opentelemetry-sdk (<2)"]
|
||||
@@ -512,6 +574,35 @@ python-versions = ">=3.7,<4.0"
|
||||
Django = ">=3.0"
|
||||
strawberry-graphql = ">=0.69.0"
|
||||
|
||||
[[package]]
|
||||
name = "svglib"
|
||||
version = "1.2.1"
|
||||
description = "A pure-Python library for reading and converting SVG"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
cssselect2 = ">=0.2.0"
|
||||
lxml = "*"
|
||||
reportlab = "*"
|
||||
tinycss2 = ">=0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "tinycss2"
|
||||
version = "1.1.1"
|
||||
description = "A tiny CSS parser"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
webencodings = ">=0.4"
|
||||
|
||||
[package.extras]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
@@ -536,6 +627,14 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2021.5"
|
||||
description = "Provider of IANA time zone data"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.8"
|
||||
@@ -567,10 +666,18 @@ six = ">=1.9.0,<2"
|
||||
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
|
||||
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "webencodings"
|
||||
version = "0.5.1"
|
||||
description = "Character encoding aliases for legacy web content"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "3fa14cb7bc28f810082b1aa4604a74060ec4ea32e63864f91a005aa37ea73c8c"
|
||||
python-versions = "~3.8"
|
||||
content-hash = "7d014e036d7cdddaa215ab1b17e7aaff3a3d4bbf34804fb52e1047bbe00607f4"
|
||||
|
||||
[metadata.files]
|
||||
asgiref = [
|
||||
@@ -585,6 +692,28 @@ attrs = [
|
||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
"backports.cached-property" = [
|
||||
{file = "backports.cached-property-1.0.1.tar.gz", hash = "sha256:1a5ef1e750f8bc7d0204c807aae8e0f450c655be0cf4b30407a35fd4bb27186c"},
|
||||
{file = "backports.cached_property-1.0.1-py3-none-any.whl", hash = "sha256:687b5fe14be40aadcf547cae91337a1fdb84026046a39370274e54d3fe4fb4f9"},
|
||||
]
|
||||
"backports.zoneinfo" = [
|
||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"},
|
||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"},
|
||||
{file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
|
||||
@@ -610,10 +739,6 @@ black = [
|
||||
{file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
|
||||
{file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
|
||||
]
|
||||
cached-property = [
|
||||
{file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"},
|
||||
{file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
|
||||
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
|
||||
@@ -630,13 +755,17 @@ colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||
]
|
||||
cssselect2 = [
|
||||
{file = "cssselect2-0.4.1-py3-none-any.whl", hash = "sha256:2f4a9f20965367bae459e3bb42561f7927e0cfe5b7ea1692757cf67ef5d7dace"},
|
||||
{file = "cssselect2-0.4.1.tar.gz", hash = "sha256:93fbb9af860e95dd40bf18c3b2b6ed99189a07c0f29ba76f9c5be71344664ec8"},
|
||||
]
|
||||
distlib = [
|
||||
{file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
|
||||
{file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
|
||||
]
|
||||
django = [
|
||||
{file = "Django-3.2.12-py3-none-any.whl", hash = "sha256:9b06c289f9ba3a8abea16c9c9505f25107809fb933676f6c891ded270039d965"},
|
||||
{file = "Django-3.2.12.tar.gz", hash = "sha256:9772e6935703e59e993960832d66a614cf0233a1c5123bc6224ecc6ad69e41e2"},
|
||||
{file = "Django-4.0.2-py3-none-any.whl", hash = "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a"},
|
||||
{file = "Django-4.0.2.tar.gz", hash = "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a"},
|
||||
]
|
||||
django-cors-headers = [
|
||||
{file = "django-cors-headers-3.11.0.tar.gz", hash = "sha256:eb98389bf7a2afc5d374806af4a9149697e3a6955b5a2dc2bf049f7d33647456"},
|
||||
@@ -646,9 +775,13 @@ django-extensions = [
|
||||
{file = "django-extensions-3.1.5.tar.gz", hash = "sha256:28e1e1bf49f0e00307ba574d645b0af3564c981a6dfc87209d48cb98f77d0b1a"},
|
||||
{file = "django_extensions-3.1.5-py3-none-any.whl", hash = "sha256:9238b9e016bb0009d621e05cf56ea8ce5cce9b32e91ad2026996a7377ca28069"},
|
||||
]
|
||||
django-rename-app = [
|
||||
{file = "django_rename_app-0.1.3-py2.py3-none-any.whl", hash = "sha256:bba31d33de4614124e2cd2f22fcea07b2affbbb2cf877a26893e0c15465fbfab"},
|
||||
{file = "django_rename_app-0.1.3.tar.gz", hash = "sha256:195d2fa09eec8ed57577f7bf57a3c6944bdb6447fe47c07be0e575fb2e325742"},
|
||||
]
|
||||
easy-thumbnails = [
|
||||
{file = "easy-thumbnails-2.7.2.tar.gz", hash = "sha256:a7dd9cf97efaf70ba5d76484a962f08ba65b31f1681bc417257743650e9e8a8a"},
|
||||
{file = "easy_thumbnails-2.7.2-py3-none-any.whl", hash = "sha256:9d7ed56a9b57aba7a8d0e03dc3b8507ca51aab5e0ed0bc705c3261ff1f659192"},
|
||||
{file = "easy-thumbnails-2.8.1.tar.gz", hash = "sha256:1a283fe8a3569c3feab4605e8279929d75c85c1151b2fd627f95b1863b5fc6c2"},
|
||||
{file = "easy_thumbnails-2.8.1-py3-none-any.whl", hash = "sha256:5f59f722508469d482d8d76b99c7b9e6c1abbce19aefd5932dc6e1b9eacd2473"},
|
||||
]
|
||||
filelock = [
|
||||
{file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
|
||||
@@ -674,6 +807,69 @@ isort = [
|
||||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
lxml = [
|
||||
{file = "lxml-4.8.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b"},
|
||||
{file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430"},
|
||||
{file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a"},
|
||||
{file = "lxml-4.8.0-cp27-cp27m-win32.whl", hash = "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5"},
|
||||
{file = "lxml-4.8.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9"},
|
||||
{file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc"},
|
||||
{file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170"},
|
||||
{file = "lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9"},
|
||||
{file = "lxml-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03"},
|
||||
{file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe"},
|
||||
{file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa"},
|
||||
{file = "lxml-4.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1"},
|
||||
{file = "lxml-4.8.0-cp310-cp310-win32.whl", hash = "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b"},
|
||||
{file = "lxml-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76"},
|
||||
{file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6"},
|
||||
{file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2"},
|
||||
{file = "lxml-4.8.0-cp35-cp35m-win32.whl", hash = "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150"},
|
||||
{file = "lxml-4.8.0-cp35-cp35m-win_amd64.whl", hash = "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-win32.whl", hash = "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429"},
|
||||
{file = "lxml-4.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-win32.whl", hash = "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63"},
|
||||
{file = "lxml-4.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-win32.whl", hash = "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696"},
|
||||
{file = "lxml-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-win32.whl", hash = "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea"},
|
||||
{file = "lxml-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c"},
|
||||
{file = "lxml-4.8.0-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507"},
|
||||
{file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9"},
|
||||
{file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e"},
|
||||
{file = "lxml-4.8.0-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0"},
|
||||
{file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79"},
|
||||
{file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93"},
|
||||
{file = "lxml-4.8.0.tar.gz", hash = "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
@@ -735,8 +931,8 @@ pillow = [
|
||||
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"},
|
||||
{file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"},
|
||||
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
|
||||
{file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
@@ -773,10 +969,6 @@ python-dateutil = [
|
||||
python-multipart = [
|
||||
{file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
|
||||
{file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||
@@ -812,13 +1004,51 @@ pyyaml = [
|
||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
reportlab = [
|
||||
{file = "reportlab-3.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fd5bc5d0b56df4cafb70b5ba8ec0bb19921574d2f99fbf623c3a215fcaa79aba"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd4d53e5d09f9912df86746330148d739ad837424383ea0b99486ac38e5edaa2"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d72e9f2d5cf20378b1c6a8ea3589728496744677332f0688dc415a8a5c91b7"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d402cd8cdd1b9b8631b6d7811c6bb788aa3caa3f38b300e9d4a5e12276d8863"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91d2952ca4264c145927303ec3c03cdf6b0b035688077b514ba37e8683c21ec"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80c9cdfed6b4bcca9dfd904d1d139b98975cd9ccb2558588307a0d53b5b6c8b4"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1890f197f12f3c734bafc21cbf9ebe18164e780164997a0d717187db2a46adc"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-win32.whl", hash = "sha256:4432abb4c61d914cbac22baa9172efe8036074cea539a12f3b37134055d38ccb"},
|
||||
{file = "reportlab-3.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:47fa377b24044514807d539780c58bc5cec91fa4ea8700f3c150c61de358d91a"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232ff75d29a1a5b3e8aa321eaec8e541afaf9704fd2e059127b816df3f2296f4"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf2e6e5b4d58ad1220228f5620064e13a53148775c28dcbe1c552f3f7e359389"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27088f0f10639f8ca27222264910c816aa3b089d590b4a1c01e9d1b0935835df"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8faa40a3f74bbf932bcda30ea8258511a3b6194460202a4720c1e4b64acb338"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec67e5bdf6cb0f3b410b1bfb75a41aeb39f89ffedd1fd5165fa87833ecbb48dd"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:439f3b2eca084aad48f6231253a33469c8eac9fcd924d80b157cbe6b77699931"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-win32.whl", hash = "sha256:6bb42e672c1866a52412888cc07a91589a28a922100770c1f53569d3e7e3ad8f"},
|
||||
{file = "reportlab-3.6.7-cp37-cp37m-win_amd64.whl", hash = "sha256:0e308e91d995beb35f0cef8c7c16d6c10c22ae937929c345f745caa6d371b7a9"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449a03741f10454d64afc55180eeac91bb22f3560332c7e25df3dc7e884cbf22"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3bda030877d3b683e4fb72fd114a6dad1e5b1ba83928ae914af5e0171d5bcff9"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7e5b0372f15331d806dd5847c4ec1f737b02438579a7e2dd798e9bf8c27c83e"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b1360785b73a52613712df48600d62788e0fb3ebd6e47fa52fb0dd498430893"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9462b443d9f0834d997039fc2648336ada3141dc96b146bcde85c81a6b3224"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:401b72f70fcac83c52853d17b0c1f1f9d93c6ceaafdd94b3c0e7b0bdfecca182"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e28735a9a01124637d637a8513ffec74a34e5fdd3c3af595eb3e0217525e8e0"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-win32.whl", hash = "sha256:a137df1507414115e3f0c8821514f450d9f9c973f2821dfa112ed797464fc93d"},
|
||||
{file = "reportlab-3.6.7-cp38-cp38-win_amd64.whl", hash = "sha256:5013b0468712dc9fbd9ace297821d27677abf427ac32fe2b537d8876bfb41c65"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:891b2ad614b43d911f70bcdefe2366db8ca7e85e952ffa96255975c1636d46e4"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fe319e0891e20fb04f038926ace02cf87f34fd223c478437572b5ed2b821f8af"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c0c5c0611c9b6b3d1aae0037efb303c57da6d1349830b83915a92bd0f5f246"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e703d862c1c85b5e377e3cf2b33763303deb80e799ad301e99d09ec81feecdb1"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13824c9363393ead2ed72907ecc40250c6d3489963627bd34e437e5accea2a1"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:51b222f2315d34a7201b1ea5d843715a2625107838de52979406d4dd5ab2f7f6"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea5ca026695210cc3364328a9490b2f29ce97b99614950df8fb11b2680641008"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-win32.whl", hash = "sha256:d5f5ab22f71433a665f50b24cced62298626753536a50db0c769c2fee07038e6"},
|
||||
{file = "reportlab-3.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:06c8dbaa26c2bf63e7fcddb86f232ddaf7c57141072407d22b4e8cbfcc5348e4"},
|
||||
{file = "reportlab-3.6.7.tar.gz", hash = "sha256:d035054724278129aba2a55c3edd6111b07e625ad1ff845e3ba2a5e4e203bbc8"},
|
||||
]
|
||||
sentinel = [
|
||||
{file = "sentinel-0.3.0-py3-none-any.whl", hash = "sha256:bd8710dd26752039c668604f6be2aaf741b56f7811c5924a4dcdfd74359244f3"},
|
||||
{file = "sentinel-0.3.0.tar.gz", hash = "sha256:f28143aa4716dbc8f6193f5682176a3c33cd26aaae05d9ecf66c186a9887cc2d"},
|
||||
]
|
||||
sentry-sdk = [
|
||||
{file = "sentry-sdk-1.4.3.tar.gz", hash = "sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828"},
|
||||
{file = "sentry_sdk-1.4.3-py2.py3-none-any.whl", hash = "sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"},
|
||||
{file = "sentry-sdk-1.5.5.tar.gz", hash = "sha256:98fd155fa5d5fec1dbabed32a1a4ae2705f1edaa5dae4e7f7b62a384ba30e759"},
|
||||
{file = "sentry_sdk-1.5.5-py2.py3-none-any.whl", hash = "sha256:3817274fba2498c8ebf6b896ee98ac916c5598706340573268c07bf2bb30d831"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
@@ -829,13 +1059,20 @@ sqlparse = [
|
||||
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
|
||||
]
|
||||
strawberry-graphql = [
|
||||
{file = "strawberry-graphql-0.93.23.tar.gz", hash = "sha256:061b1c54c69dd7e11cb2b7e7b474392e4b32943789e7ac0b47954fb965999d31"},
|
||||
{file = "strawberry_graphql-0.93.23-py3-none-any.whl", hash = "sha256:b2a9a4af0a491e1626aafc420e484f551f89eacb13c0e48de9ab5dd8f7e5ca34"},
|
||||
{file = "strawberry-graphql-0.97.0.tar.gz", hash = "sha256:0bd486111166da1d6e48a2c95de437096ca2ba28e2b2963b64a93136c54bef02"},
|
||||
{file = "strawberry_graphql-0.97.0-py3-none-any.whl", hash = "sha256:2f304822c6ecc866e07f11618fbdf826676ab61af5f757794805ee1853504603"},
|
||||
]
|
||||
strawberry-graphql-django = [
|
||||
{file = "strawberry-graphql-django-0.2.5.tar.gz", hash = "sha256:483d97c24ce440a1e3cb92ec3e1fdc0cf534d9afa79600396b0a27a5d9d97793"},
|
||||
{file = "strawberry_graphql_django-0.2.5-py3-none-any.whl", hash = "sha256:dee8011bf074c618b7ddab0a92d0832b0ceea2dcb69ee422003a98e4a28c14d6"},
|
||||
]
|
||||
svglib = [
|
||||
{file = "svglib-1.2.1.tar.gz", hash = "sha256:c77a0702fafd367c0fdca08ca1b7e1ee10058bde3bae252f49a3836e51e54519"},
|
||||
]
|
||||
tinycss2 = [
|
||||
{file = "tinycss2-1.1.1-py3-none-any.whl", hash = "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8"},
|
||||
{file = "tinycss2-1.1.1.tar.gz", hash = "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
@@ -848,6 +1085,10 @@ typing-extensions = [
|
||||
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
||||
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
||||
]
|
||||
tzdata = [
|
||||
{file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"},
|
||||
{file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
|
||||
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
|
||||
@@ -856,3 +1097,7 @@ virtualenv = [
|
||||
{file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"},
|
||||
{file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"},
|
||||
]
|
||||
webencodings = [
|
||||
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
|
||||
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
|
||||
]
|
||||
+7
-6
@@ -1,5 +1,5 @@
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "bbssite.settings"
|
||||
DJANGO_SETTINGS_MODULE = "bbs.settings"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
@@ -17,17 +17,18 @@ authors = ["Nikolai R Kristiansen <nikolaik@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = "~3.8"
|
||||
django-extensions = "^3.1.3"
|
||||
Django = "^3.2.8"
|
||||
easy-thumbnails = "2.7.2"
|
||||
Django = "^4.0.0"
|
||||
easy-thumbnails = "^2.8.1"
|
||||
gunicorn = "^20.1.0"
|
||||
mysqlclient = "^2.0.3"
|
||||
Pillow = "^9.0.0"
|
||||
sentry-sdk = "1.4.3"
|
||||
strawberry-graphql = "^0.93.1"
|
||||
sentry-sdk = "^1.5.5"
|
||||
strawberry-graphql = "^0.97.0"
|
||||
strawberry-graphql-django = "^0.2.5"
|
||||
django-cors-headers = "^3.10.0"
|
||||
django-rename-app = "^0.1.3"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "*"
|
||||
|
||||
Reference in new issue
Block a user