Free and open source ticket system written in python
0
fork

Configure Feed

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

📦 NEW: Add Google SSO functionality

+838 -6
+19
core/forms.py
··· 58 58 "An account with this email already exists" 59 59 ) 60 60 return cleaned_data 61 + 62 + 63 + class AccountFinishForm(forms.Form): 64 + username = forms.CharField(required=True, widget=forms.TextInput( 65 + attrs={'class': 'input input-bordered w-full'})) 66 + 67 + class Meta: 68 + model = PawUser 69 + fields = ('username') 70 + 71 + def clean(self): 72 + cleaned_data = super(AccountFinishForm, self).clean() 73 + 74 + if PawUser.objects.filter(username=cleaned_data.get("username")).exists(): 75 + raise forms.ValidationError( 76 + "An account with this username already exists" 77 + ) 78 + 79 + return cleaned_data
+41
core/migrations/0003_googlessouser.py
··· 1 + # Generated by Django 5.0.3 on 2024-03-10 18:32 2 + 3 + import django.db.models.deletion 4 + from django.conf import settings 5 + from django.db import migrations, models 6 + 7 + 8 + class Migration(migrations.Migration): 9 + 10 + dependencies = [ 11 + ("core", "0002_pawuser_use_darkmode"), 12 + ] 13 + 14 + operations = [ 15 + migrations.CreateModel( 16 + name="GoogleSSOUser", 17 + fields=[ 18 + ( 19 + "id", 20 + models.BigAutoField( 21 + auto_created=True, 22 + primary_key=True, 23 + serialize=False, 24 + verbose_name="ID", 25 + ), 26 + ), 27 + ("google_id", models.CharField(max_length=255)), 28 + ( 29 + "user", 30 + models.OneToOneField( 31 + on_delete=django.db.models.deletion.CASCADE, 32 + to=settings.AUTH_USER_MODEL, 33 + ), 34 + ), 35 + ], 36 + options={ 37 + "verbose_name": "Google SSO User", 38 + "db_table": "google_sso_user", 39 + }, 40 + ), 41 + ]
+13
core/models.py
··· 1 1 from django.db import models 2 2 from django.contrib.auth.models import AbstractUser 3 + from django.utils.translation import gettext_lazy as _ 3 4 4 5 5 6 class PawUser(AbstractUser): ··· 11 12 12 13 def __str__(self): 13 14 return self.username 15 + 16 + 17 + class GoogleSSOUser(models.Model): 18 + user = models.OneToOneField(PawUser, on_delete=models.CASCADE) 19 + google_id = models.CharField(max_length=255) 20 + 21 + def __str__(self): 22 + return self.paw_user.username 23 + 24 + class Meta: 25 + db_table = "google_sso_user" 26 + verbose_name = _("Google SSO User")
+3 -1
core/urls.py
··· 1 1 from django.urls import path 2 2 3 - from .views import home_view, logout_view, settings_view, register_view 3 + from .views import home_view, logout_view, settings_view, register_view, login_view, google_callback_view 4 4 5 5 urlpatterns = [ 6 6 path("", home_view, name="home"), 7 7 path("settings", settings_view, name="settings"), 8 8 path("register", register_view, name="register"), 9 + path("login", login_view, name="login"), 9 10 path("logout", logout_view, name="logout"), 11 + path("callback/google", google_callback_view, name="google_callback"), 10 12 ]
+46
core/utils/google_sso.py
··· 1 + from dataclasses import dataclass 2 + from typing import Any, Optional 3 + 4 + from django.utils.translation import gettext_lazy as _ 5 + from google.oauth2.credentials import Credentials 6 + from google_auth_oauthlib.flow import Flow 7 + from django.conf import settings 8 + 9 + 10 + @dataclass 11 + class GoogleSSO: 12 + _flow: Optional[Flow] = None 13 + _userinfo: Optional[dict[Any, Any]] = None 14 + 15 + @staticmethod 16 + def get_client_config() -> Credentials: 17 + return { 18 + "web": { 19 + "client_id": settings.GOOGLE_OAUTH_CLIENT_ID, 20 + "project_id": settings.GOOGLE_OAUTH_PROJECT_ID, 21 + "auth_uri": "https://accounts.google.com/o/oauth2/auth", 22 + "token_uri": "https://oauth2.googleapis.com/token", 23 + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 24 + "client_secret": settings.GOOGLE_OAUTH_CLIENT_SECRET, 25 + "redirect_uris": [settings.GOOGLE_OAUTH_REDIRECT_URI], 26 + } 27 + } 28 + 29 + @property 30 + def flow(self) -> Flow: 31 + if not self._flow: 32 + self._flow = Flow.from_client_config( 33 + self.get_client_config(), 34 + scopes=settings.GOOGLE_OAUTH_SCOPES, 35 + redirect_uri=settings.GOOGLE_OAUTH_REDIRECT_URI 36 + ) 37 + return self._flow 38 + 39 + def get_user_info(self) -> dict: 40 + session = self.flow.authorized_session() 41 + user_info = session.get( 42 + "https://www.googleapis.com/oauth2/v2/userinfo").json() 43 + return user_info 44 + 45 + def get_user_token(self): 46 + return self.flow.credentials.token
+64 -2
core/views.py
··· 1 1 from django.shortcuts import render, redirect 2 - from django.contrib.auth import logout 3 - from .forms import UserChangeForm, RegisterForm 2 + from django.contrib.auth import logout, login 3 + from .forms import UserChangeForm, RegisterForm, AccountFinishForm 4 + from django.contrib.auth.forms import AuthenticationForm 4 5 from .models import PawUser 5 6 from django.utils import translation 6 7 from django.conf import settings 7 8 from django.contrib.auth.decorators import login_required 9 + from .utils.google_sso import GoogleSSO 8 10 9 11 10 12 @login_required ··· 32 34 form = RegisterForm() 33 35 34 36 return render(request, "core/register.html", {"form": form}) 37 + 38 + 39 + def login_view(request): 40 + 41 + if settings.GOOGLE_OAUTH_ENABLED: 42 + google_sso = GoogleSSO() 43 + auth_url, state = google_sso.flow.authorization_url(prompt="consent") 44 + 45 + # Save data on Session 46 + if not request.session.session_key: 47 + request.session.create() 48 + request.session.set_expiry(60 * 1000) 49 + request.session["sso_state"] = state 50 + # request.session["sso_next_url"] = next_path 51 + request.session.save() 52 + 53 + if request.method == "POST": 54 + form = AuthenticationForm(data=request.POST) 55 + if form.is_valid(): 56 + return redirect("home") 57 + else: 58 + form = AuthenticationForm() 59 + 60 + return render(request, "core/login.html", {"form": form, "google_sso_enabled": settings.GOOGLE_OAUTH_ENABLED, "google_sso_auth_url": auth_url}) 61 + 62 + 63 + def google_callback_view(request): 64 + 65 + if not settings.GOOGLE_OAUTH_ENABLED: 66 + return redirect("login") 67 + 68 + if request.method == "POST" and request.user.is_authenticated and "account_finish" in request.POST: 69 + form = AccountFinishForm(request.POST) 70 + if form.is_valid(): 71 + request.user.username = form.cleaned_data["username"] 72 + request.user.save() 73 + return redirect("home") 74 + else: 75 + return render(request, "core/account_finish.html", {"form": form}) 76 + else: 77 + google_sso = GoogleSSO() 78 + state = request.GET.get("state") 79 + code = request.GET.get("code") 80 + 81 + if state != request.session.get("sso_state"): 82 + return redirect("login") 83 + 84 + try: 85 + google_sso.flow.fetch_token(code=code) 86 + user_info = google_sso.get_user_info() 87 + except Exception: 88 + return redirect("login") 89 + 90 + user, created = PawUser.objects.get_or_create(email=user_info["email"]) 91 + login(request, user) 92 + if created or not user.username: 93 + form = AccountFinishForm() 94 + return render(request, "core/account_finish.html", {"form": form}) 95 + 96 + return redirect("home") 35 97 36 98 37 99 def logout_view(request):
+1 -1
paw/__init__.py
··· 1 1 from django import get_version 2 2 3 - VERSION = (0, 1, 0, "beta", 9) 3 + VERSION = (0, 1, 0, "beta", 10) 4 4 5 5 __version__ = get_version(VERSION)
+12
paw/settings.py
··· 148 148 DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 149 149 150 150 LOGIN_REDIRECT_URL = "/tickets" 151 + 152 + # Google SSO 153 + GOOGLE_OAUTH_ENABLED = True 154 + GOOGLE_OAUTH_CLIENT_ID = "" 155 + GOOGLE_OAUTH_PROJECT_ID = "" 156 + GOOGLE_OAUTH_CLIENT_SECRET = "" 157 + GOOGLE_OAUTH_REDIRECT_URI = "http://localhost:8000/callback/google" 158 + GOOGLE_OAUTH_SCOPES = [ 159 + "openid", 160 + "https://www.googleapis.com/auth/userinfo.profile", 161 + "https://www.googleapis.com/auth/userinfo.email" 162 + ]
+40
paw/templates/core/account_finish.html
··· 1 + <!-- templates/core/account_finish.html --> 2 + {% extends 'base.html' %} 3 + {% block content %} 4 + {% load i18n %} 5 + <div class="flex flex-col w-full max-w-xl mx-auto h-full min-h-screen justify-center"> 6 + <div class="p-4"> 7 + <div class="mb-4"> 8 + {% include 'partials/logo.html' with responsive=False %} 9 + </div> 10 + <h1 class="text-3xl font-bold p-2">{% trans 'Register a new account' %}</h1> 11 + <div class="bg-base-200 rounded p-8"> 12 + <ul class="steps w-full mb-6"> 13 + <li class="step step-accent">{% trans 'Authenticate' %}</li> 14 + <li class="step step-accent">{% trans 'Set up account' %}</li> 15 + <li class="step step-success">{% trans 'Done' %}</li> 16 + </ul> 17 + <form method="post"> 18 + {% csrf_token %} 19 + 20 + {% if form.non_field_errors %} 21 + <div role="alert" class="alert alert-error mb-4"> 22 + <svg xmlns="http://www.w3.org/2000/svg" class="hidden sm:block stroke-current shrink-0 h-6 w-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 10l4 4m0 -4l-4 4" /><path d="M12 3c7.2 0 9 1.8 9 9s-1.8 9 -9 9s-9 -1.8 -9 -9s1.8 -9 9 -9z" /></svg> 23 + <span>{{ form.non_field_errors }}</span> 24 + </div> 25 + {% endif %} 26 + 27 + <div> 28 + <label class="label"> 29 + <span class="text-base label-text" for="{{ form.username.id_for_label }}">{% trans 'Username' %}</span> 30 + </label> 31 + {{ form.username }} 32 + </div> 33 + <div class="flex justify-end mt-4"> 34 + <button type="submit" name="account_finish" class="btn btn-success">{% trans 'Save' %}</button> 35 + </div> 36 + </form> 37 + </div> 38 + </div> 39 + </div> 40 + {% endblock %}
+41
paw/templates/core/login.html
··· 1 + <!-- templates/core/login.html --> 2 + {% extends 'base.html' %} 3 + {% block content %} 4 + {% load i18n %} 5 + <div class="flex flex-col w-full max-w-xl mx-auto h-full min-h-screen justify-center"> 6 + <div class="p-4"> 7 + <div class="mb-4"> 8 + {% include 'partials/logo.html' with responsive=False %} 9 + </div> 10 + <h1 class="text-3xl font-bold p-2">Log In</h1> 11 + <div class="bg-base-200 rounded p-8"> 12 + <form method="post"> 13 + {% csrf_token %} 14 + <div> 15 + <label class="label"> 16 + <span class="text-base label-text" for="{{ form.username.id_for_label }}">Username</span> 17 + </label> 18 + <input type="text" name="username" placeholder="Username" class="w-full input input-bordered" /> 19 + </div> 20 + <div> 21 + <label class="label"> 22 + <span class="text-base label-text" for="{{ form.password.id_for_label }}">Password</span> 23 + </label> 24 + <input type="password" name="password" placeholder="Enter Password" 25 + class="w-full input input-bordered" /> 26 + </div> 27 + <div class="flex justify-end mt-4"> 28 + <button type="submit" class="btn btn-accent">Log In</button> 29 + </div> 30 + </form> 31 + {% if google_sso_enabled %} 32 + <div class="divider"></div> 33 + <a href="{{ google_sso_auth_url }}" class="btn w-full bg-[#4285F4] hover:bg-[#4285F4]/90 border-[#4285F4] text-white"> 34 + <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 2a9.96 9.96 0 0 1 6.29 2.226a1 1 0 0 1 .04 1.52l-1.51 1.362a1 1 0 0 1 -1.265 .06a6 6 0 1 0 2.103 6.836l.001 -.004h-3.66a1 1 0 0 1 -.992 -.883l-.007 -.117v-2a1 1 0 0 1 1 -1h6.945a1 1 0 0 1 .994 .89c.04 .367 .061 .737 .061 1.11c0 5.523 -4.477 10 -10 10s-10 -4.477 -10 -10s4.477 -10 10 -10z" stroke-width="0" fill="currentColor" /></svg> 35 + Log in with Google 36 + </a> 37 + {% endif %} 38 + </div> 39 + </div> 40 + </div> 41 + {% endblock %}
+1 -1
paw/templates/core/register.html
··· 8 8 {% include 'partials/logo.html' with responsive=False %} 9 9 </div> 10 10 <h1 class="text-3xl font-bold p-2">Register a new account</h1> 11 - <div class="bg-base-300 rounded p-8"> 11 + <div class="bg-base-200 rounded p-8"> 12 12 <form method="post"> 13 13 {% csrf_token %} 14 14
+285 -1
poetry.lock
··· 15 15 tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] 16 16 17 17 [[package]] 18 + name = "cachetools" 19 + version = "5.3.3" 20 + description = "Extensible memoizing collections and decorators" 21 + optional = false 22 + python-versions = ">=3.7" 23 + files = [ 24 + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, 25 + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, 26 + ] 27 + 28 + [[package]] 29 + name = "certifi" 30 + version = "2024.2.2" 31 + description = "Python package for providing Mozilla's CA Bundle." 32 + optional = false 33 + python-versions = ">=3.6" 34 + files = [ 35 + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, 36 + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, 37 + ] 38 + 39 + [[package]] 40 + name = "charset-normalizer" 41 + version = "3.3.2" 42 + description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 43 + optional = false 44 + python-versions = ">=3.7.0" 45 + files = [ 46 + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 47 + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 48 + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 49 + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 50 + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 51 + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 52 + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 53 + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 54 + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 55 + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 56 + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 57 + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 58 + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 59 + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 60 + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 61 + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 62 + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 63 + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 64 + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 65 + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 66 + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 67 + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 68 + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 69 + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 70 + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 71 + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 72 + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 73 + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 74 + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 75 + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 76 + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 77 + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 78 + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 79 + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 80 + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 81 + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 82 + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 83 + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 84 + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 85 + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 86 + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 87 + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 88 + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 89 + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 90 + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 91 + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 92 + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 93 + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 94 + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 95 + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 96 + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 97 + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 98 + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 99 + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 100 + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 101 + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 102 + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 103 + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 104 + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 105 + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 106 + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 107 + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 108 + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 109 + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 110 + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 111 + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 112 + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 113 + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 114 + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 115 + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 116 + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 117 + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 118 + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 119 + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 120 + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 121 + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 122 + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 123 + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 124 + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 125 + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 126 + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 127 + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 128 + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 129 + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 130 + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 131 + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 132 + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 133 + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 134 + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 135 + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 136 + ] 137 + 138 + [[package]] 18 139 name = "django" 19 140 version = "5.0.3" 20 141 description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." ··· 49 170 Pillow = ">=9.0.0" 50 171 51 172 [[package]] 173 + name = "google-auth" 174 + version = "2.28.2" 175 + description = "Google Authentication Library" 176 + optional = false 177 + python-versions = ">=3.7" 178 + files = [ 179 + {file = "google-auth-2.28.2.tar.gz", hash = "sha256:80b8b4969aa9ed5938c7828308f20f035bc79f9d8fb8120bf9dc8db20b41ba30"}, 180 + {file = "google_auth-2.28.2-py2.py3-none-any.whl", hash = "sha256:9fd67bbcd40f16d9d42f950228e9cf02a2ded4ae49198b27432d0cded5a74c38"}, 181 + ] 182 + 183 + [package.dependencies] 184 + cachetools = ">=2.0.0,<6.0" 185 + pyasn1-modules = ">=0.2.1" 186 + rsa = ">=3.1.4,<5" 187 + 188 + [package.extras] 189 + aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] 190 + enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] 191 + pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] 192 + reauth = ["pyu2f (>=0.1.5)"] 193 + requests = ["requests (>=2.20.0,<3.0.0.dev0)"] 194 + 195 + [[package]] 196 + name = "google-auth-oauthlib" 197 + version = "1.2.0" 198 + description = "Google Authentication Library" 199 + optional = false 200 + python-versions = ">=3.6" 201 + files = [ 202 + {file = "google-auth-oauthlib-1.2.0.tar.gz", hash = "sha256:292d2d3783349f2b0734a0a0207b1e1e322ac193c2c09d8f7c613fb7cc501ea8"}, 203 + {file = "google_auth_oauthlib-1.2.0-py2.py3-none-any.whl", hash = "sha256:297c1ce4cb13a99b5834c74a1fe03252e1e499716718b190f56bcb9c4abc4faf"}, 204 + ] 205 + 206 + [package.dependencies] 207 + google-auth = ">=2.15.0" 208 + requests-oauthlib = ">=0.7.0" 209 + 210 + [package.extras] 211 + tool = ["click (>=6.0.0)"] 212 + 213 + [[package]] 214 + name = "idna" 215 + version = "3.6" 216 + description = "Internationalized Domain Names in Applications (IDNA)" 217 + optional = false 218 + python-versions = ">=3.5" 219 + files = [ 220 + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 221 + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 222 + ] 223 + 224 + [[package]] 225 + name = "oauthlib" 226 + version = "3.2.2" 227 + description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" 228 + optional = false 229 + python-versions = ">=3.6" 230 + files = [ 231 + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, 232 + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, 233 + ] 234 + 235 + [package.extras] 236 + rsa = ["cryptography (>=3.0.0)"] 237 + signals = ["blinker (>=1.4.0)"] 238 + signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] 239 + 240 + [[package]] 52 241 name = "pillow" 53 242 version = "10.2.0" 54 243 description = "Python Imaging Library (Fork)" ··· 134 323 xmp = ["defusedxml"] 135 324 136 325 [[package]] 326 + name = "pyasn1" 327 + version = "0.5.1" 328 + description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" 329 + optional = false 330 + python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 331 + files = [ 332 + {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, 333 + {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, 334 + ] 335 + 336 + [[package]] 337 + name = "pyasn1-modules" 338 + version = "0.3.0" 339 + description = "A collection of ASN.1-based protocols modules" 340 + optional = false 341 + python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 342 + files = [ 343 + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, 344 + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, 345 + ] 346 + 347 + [package.dependencies] 348 + pyasn1 = ">=0.4.6,<0.6.0" 349 + 350 + [[package]] 351 + name = "requests" 352 + version = "2.31.0" 353 + description = "Python HTTP for Humans." 354 + optional = false 355 + python-versions = ">=3.7" 356 + files = [ 357 + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 358 + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 359 + ] 360 + 361 + [package.dependencies] 362 + certifi = ">=2017.4.17" 363 + charset-normalizer = ">=2,<4" 364 + idna = ">=2.5,<4" 365 + urllib3 = ">=1.21.1,<3" 366 + 367 + [package.extras] 368 + socks = ["PySocks (>=1.5.6,!=1.5.7)"] 369 + use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 370 + 371 + [[package]] 372 + name = "requests-oauthlib" 373 + version = "1.3.1" 374 + description = "OAuthlib authentication support for Requests." 375 + optional = false 376 + python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 377 + files = [ 378 + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, 379 + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, 380 + ] 381 + 382 + [package.dependencies] 383 + oauthlib = ">=3.0.0" 384 + requests = ">=2.0.0" 385 + 386 + [package.extras] 387 + rsa = ["oauthlib[signedtoken] (>=3.0.0)"] 388 + 389 + [[package]] 390 + name = "rsa" 391 + version = "4.9" 392 + description = "Pure-Python RSA implementation" 393 + optional = false 394 + python-versions = ">=3.6,<4" 395 + files = [ 396 + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, 397 + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, 398 + ] 399 + 400 + [package.dependencies] 401 + pyasn1 = ">=0.1.3" 402 + 403 + [[package]] 137 404 name = "sqlparse" 138 405 version = "0.4.4" 139 406 description = "A non-validating SQL parser." ··· 160 427 {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, 161 428 ] 162 429 430 + [[package]] 431 + name = "urllib3" 432 + version = "2.2.1" 433 + description = "HTTP library with thread-safe connection pooling, file post, and more." 434 + optional = false 435 + python-versions = ">=3.8" 436 + files = [ 437 + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, 438 + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, 439 + ] 440 + 441 + [package.extras] 442 + brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 443 + h2 = ["h2 (>=4,<5)"] 444 + socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 445 + zstd = ["zstandard (>=0.18.0)"] 446 + 163 447 [metadata] 164 448 lock-version = "2.0" 165 449 python-versions = "^3.12" 166 - content-hash = "b6020615bc7ed46644a57c2e13c1789753c19e0518c44b4b92ebc918d03f802c" 450 + content-hash = "1e0820737369c3e1204a95a07ea47b332c4d30daadc349c61dba044b8e49ffb7"
+2
pyproject.toml
··· 10 10 python = "^3.12" 11 11 Django = "^5.0.3" 12 12 django-colorfield = "^0.11.0" 13 + google-auth = "^2.28.2" 14 + google-auth-oauthlib = "^1.2.0" 13 15 14 16 15 17 [build-system]
+270
static/css/paw.css
··· 1152 1152 } 1153 1153 } 1154 1154 1155 + .btn-outline.btn-primary:hover { 1156 + --tw-text-opacity: 1; 1157 + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); 1158 + } 1159 + 1160 + @supports (color: color-mix(in oklab, black, black)) { 1161 + .btn-outline.btn-primary:hover { 1162 + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); 1163 + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); 1164 + } 1165 + } 1166 + 1155 1167 .btn-outline.btn-accent:hover { 1156 1168 --tw-text-opacity: 1; 1157 1169 color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); ··· 1477 1489 font-weight: 800; 1478 1490 } 1479 1491 1492 + .steps { 1493 + display: inline-grid; 1494 + grid-auto-flow: column; 1495 + overflow: hidden; 1496 + overflow-x: auto; 1497 + counter-reset: step; 1498 + grid-auto-columns: 1fr; 1499 + } 1500 + 1501 + .steps .step { 1502 + display: grid; 1503 + grid-template-columns: repeat(1, minmax(0, 1fr)); 1504 + grid-template-columns: auto; 1505 + grid-template-rows: repeat(2, minmax(0, 1fr)); 1506 + grid-template-rows: 40px 1fr; 1507 + place-items: center; 1508 + text-align: center; 1509 + min-width: 4rem; 1510 + } 1511 + 1480 1512 .table { 1481 1513 position: relative; 1482 1514 width: 100%; ··· 1637 1669 border-color: var(--btn-color, var(--fallback-b2)); 1638 1670 } 1639 1671 1672 + .btn-primary { 1673 + --btn-color: var(--fallback-p); 1674 + } 1675 + 1640 1676 .btn-accent { 1641 1677 --btn-color: var(--fallback-a); 1642 1678 } ··· 1659 1695 } 1660 1696 1661 1697 @supports (color: color-mix(in oklab, black, black)) { 1698 + .btn-outline.btn-primary.btn-active { 1699 + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); 1700 + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); 1701 + } 1702 + 1662 1703 .btn-outline.btn-accent.btn-active { 1663 1704 background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); 1664 1705 border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); ··· 1686 1727 outline-offset: 2px; 1687 1728 } 1688 1729 1730 + .btn-primary { 1731 + --tw-text-opacity: 1; 1732 + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); 1733 + outline-color: var(--fallback-p,oklch(var(--p)/1)); 1734 + } 1735 + 1689 1736 @supports (color: oklch(0 0 0)) { 1737 + .btn-primary { 1738 + --btn-color: var(--p); 1739 + } 1740 + 1690 1741 .btn-accent { 1691 1742 --btn-color: var(--a); 1692 1743 } ··· 1764 1815 .btn-ghost.btn-active { 1765 1816 border-color: transparent; 1766 1817 background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); 1818 + } 1819 + 1820 + .btn-outline.btn-primary { 1821 + --tw-text-opacity: 1; 1822 + color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity))); 1823 + } 1824 + 1825 + .btn-outline.btn-primary.btn-active { 1826 + --tw-text-opacity: 1; 1827 + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); 1767 1828 } 1768 1829 1769 1830 .btn-outline.btn-accent { ··· 2232 2293 --tw-divide-x-reverse: 1; 2233 2294 } 2234 2295 2296 + .steps .step:before { 2297 + top: 0px; 2298 + grid-column-start: 1; 2299 + grid-row-start: 1; 2300 + height: 0.5rem; 2301 + width: 100%; 2302 + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 2303 + --tw-bg-opacity: 1; 2304 + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); 2305 + --tw-text-opacity: 1; 2306 + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); 2307 + content: ""; 2308 + margin-inline-start: -100%; 2309 + } 2310 + 2311 + .steps .step:after { 2312 + content: counter(step); 2313 + counter-increment: step; 2314 + z-index: 1; 2315 + position: relative; 2316 + grid-column-start: 1; 2317 + grid-row-start: 1; 2318 + display: grid; 2319 + height: 2rem; 2320 + width: 2rem; 2321 + place-items: center; 2322 + place-self: center; 2323 + border-radius: 9999px; 2324 + --tw-bg-opacity: 1; 2325 + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); 2326 + --tw-text-opacity: 1; 2327 + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); 2328 + } 2329 + 2330 + .steps .step:first-child:before { 2331 + content: none; 2332 + } 2333 + 2334 + .steps .step[data-content]:after { 2335 + content: attr(data-content); 2336 + } 2337 + 2338 + .steps .step-neutral + .step-neutral:before, 2339 + .steps .step-neutral:after { 2340 + --tw-bg-opacity: 1; 2341 + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); 2342 + --tw-text-opacity: 1; 2343 + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); 2344 + } 2345 + 2346 + .steps .step-primary + .step-primary:before, 2347 + .steps .step-primary:after { 2348 + --tw-bg-opacity: 1; 2349 + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); 2350 + --tw-text-opacity: 1; 2351 + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); 2352 + } 2353 + 2354 + .steps .step-secondary + .step-secondary:before, 2355 + .steps .step-secondary:after { 2356 + --tw-bg-opacity: 1; 2357 + background-color: var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity))); 2358 + --tw-text-opacity: 1; 2359 + color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity))); 2360 + } 2361 + 2362 + .steps .step-accent + .step-accent:before, 2363 + .steps .step-accent:after { 2364 + --tw-bg-opacity: 1; 2365 + background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity))); 2366 + --tw-text-opacity: 1; 2367 + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); 2368 + } 2369 + 2370 + .steps .step-info + .step-info:before { 2371 + --tw-bg-opacity: 1; 2372 + background-color: var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity))); 2373 + } 2374 + 2375 + .steps .step-info:after { 2376 + --tw-bg-opacity: 1; 2377 + background-color: var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity))); 2378 + --tw-text-opacity: 1; 2379 + color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity))); 2380 + } 2381 + 2382 + .steps .step-success + .step-success:before { 2383 + --tw-bg-opacity: 1; 2384 + background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity))); 2385 + } 2386 + 2387 + .steps .step-success:after { 2388 + --tw-bg-opacity: 1; 2389 + background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity))); 2390 + --tw-text-opacity: 1; 2391 + color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity))); 2392 + } 2393 + 2394 + .steps .step-warning + .step-warning:before { 2395 + --tw-bg-opacity: 1; 2396 + background-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity))); 2397 + } 2398 + 2399 + .steps .step-warning:after { 2400 + --tw-bg-opacity: 1; 2401 + background-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity))); 2402 + --tw-text-opacity: 1; 2403 + color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity))); 2404 + } 2405 + 2406 + .steps .step-error + .step-error:before { 2407 + --tw-bg-opacity: 1; 2408 + background-color: var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity))); 2409 + } 2410 + 2411 + .steps .step-error:after { 2412 + --tw-bg-opacity: 1; 2413 + background-color: var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity))); 2414 + --tw-text-opacity: 1; 2415 + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); 2416 + } 2417 + 2235 2418 :is([dir="rtl"] .table) { 2236 2419 text-align: right; 2237 2420 } ··· 2506 2689 padding-right: 0.75rem; 2507 2690 } 2508 2691 2692 + .steps-horizontal .step { 2693 + display: grid; 2694 + grid-template-columns: repeat(1, minmax(0, 1fr)); 2695 + grid-template-rows: repeat(2, minmax(0, 1fr)); 2696 + place-items: center; 2697 + text-align: center; 2698 + } 2699 + 2700 + .steps-vertical .step { 2701 + display: grid; 2702 + grid-template-columns: repeat(2, minmax(0, 1fr)); 2703 + grid-template-rows: repeat(1, minmax(0, 1fr)); 2704 + } 2705 + 2509 2706 .avatar.online:before { 2510 2707 content: ""; 2511 2708 position: absolute; ··· 2552 2749 margin-inline-start: -1px; 2553 2750 } 2554 2751 2752 + .steps-horizontal .step { 2753 + grid-template-rows: 40px 1fr; 2754 + grid-template-columns: auto; 2755 + min-width: 4rem; 2756 + } 2757 + 2758 + .steps-horizontal .step:before { 2759 + height: 0.5rem; 2760 + width: 100%; 2761 + --tw-translate-x: 0px; 2762 + --tw-translate-y: 0px; 2763 + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 2764 + content: ""; 2765 + margin-inline-start: -100%; 2766 + } 2767 + 2768 + :is([dir="rtl"] .steps-horizontal .step):before { 2769 + --tw-translate-x: 0px; 2770 + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 2771 + } 2772 + 2773 + .steps-vertical .step { 2774 + gap: 0.5rem; 2775 + grid-template-columns: 40px 1fr; 2776 + grid-template-rows: auto; 2777 + min-height: 4rem; 2778 + justify-items: start; 2779 + } 2780 + 2781 + .steps-vertical .step:before { 2782 + height: 100%; 2783 + width: 0.5rem; 2784 + --tw-translate-x: -50%; 2785 + --tw-translate-y: -50%; 2786 + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 2787 + margin-inline-start: 50%; 2788 + } 2789 + 2790 + :is([dir="rtl"] .steps-vertical .step):before { 2791 + --tw-translate-x: 50%; 2792 + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 2793 + } 2794 + 2555 2795 .static { 2556 2796 position: static; 2557 2797 } ··· 2815 3055 border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); 2816 3056 } 2817 3057 3058 + .border-\[\#4285F4\] { 3059 + --tw-border-opacity: 1; 3060 + border-color: rgb(66 133 244 / var(--tw-border-opacity)); 3061 + } 3062 + 2818 3063 .bg-base-200 { 2819 3064 --tw-bg-opacity: 1; 2820 3065 background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); ··· 2828 3073 .bg-neutral { 2829 3074 --tw-bg-opacity: 1; 2830 3075 background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); 3076 + } 3077 + 3078 + .bg-\[\#4285F4\] { 3079 + --tw-bg-opacity: 1; 3080 + background-color: rgb(66 133 244 / var(--tw-bg-opacity)); 3081 + } 3082 + 3083 + .bg-slate-300 { 3084 + --tw-bg-opacity: 1; 3085 + background-color: rgb(203 213 225 / var(--tw-bg-opacity)); 2831 3086 } 2832 3087 2833 3088 .stroke-current { ··· 2948 3203 color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity))); 2949 3204 } 2950 3205 3206 + .text-white { 3207 + --tw-text-opacity: 1; 3208 + color: rgb(255 255 255 / var(--tw-text-opacity)); 3209 + } 3210 + 2951 3211 .underline { 2952 3212 text-decoration-line: underline; 2953 3213 } ··· 2958 3218 2959 3219 .opacity-70 { 2960 3220 opacity: 0.7; 3221 + } 3222 + 3223 + .shadow { 3224 + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 3225 + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 3226 + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 2961 3227 } 2962 3228 2963 3229 @media (min-width: 1024px) { ··· 3002 3268 border-radius: 9999px; 3003 3269 padding: 0px; 3004 3270 } 3271 + } 3272 + 3273 + .hover\:bg-\[\#4285F4\]\/90:hover { 3274 + background-color: rgb(66 133 244 / 0.9); 3005 3275 } 3006 3276 3007 3277 @media (min-width: 640px) {