Use Bootstrap

This commit is contained in:
2024-07-02 03:10:47 +02:00
parent e2f7c13b79
commit 2119eead14
12 changed files with 288 additions and 360 deletions

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
static/bootstrap.min.js.map linguist-vendored
static/bootstrap.min.css.map linguist-vendored
static/bootstrap.min.js linguist-vendored
static/bootstrap.min.css linguist-vendored

View File

@@ -2,11 +2,13 @@ import os
from pathlib import Path
import sentry_sdk
from django.contrib import messages
from dotenv import find_dotenv, load_dotenv
from platformdirs import user_data_dir
load_dotenv(dotenv_path=find_dotenv(), verbose=True)
DATA_DIR = Path(
user_data_dir(
appname="TTVDrops",
@@ -181,3 +183,12 @@ SOCIALACCOUNT_PROVIDERS = {
"AUTH_PARAMS": {"force_verify": True},
},
}
MESSAGE_TAGS: dict[int, str] = {
messages.DEBUG: "alert-info",
messages.INFO: "alert-info",
messages.SUCCESS: "alert-success",
messages.WARNING: "alert-warning",
messages.ERROR: "alert-danger",
}

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.0.6 on 2024-07-01 21:47
import django.utils.timezone
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("core", "0004_historicaldiscordsetting_historicalsubscription"),
]
operations: list[Operation] = [
migrations.AddField(
model_name="discordsetting",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
),
preserve_default=False,
),
migrations.AddField(
model_name="historicaldiscordsetting",
name="created_at",
field=models.DateTimeField(
blank=True,
default=django.utils.timezone.now,
editable=False,
),
preserve_default=False,
),
]

View File

@@ -11,6 +11,7 @@ class DiscordSetting(models.Model):
webhook_url = models.URLField()
history = HistoricalRecords()
disabled = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return f"Discord: {self.user.username} - {self.name}"

View File

@@ -1,206 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="description" content="Twitch Drops">
<meta name="author" content="TheLovinator">
<meta name="keywords" content="Twitch, Drops, Twitch Drops">
<meta name="robots" content="index, follow">
<title>Add Discord Webhook</title>
<style>
:root {
--background-color: #121212;
--text-color: #e0e0e0;
--header-background: #1e1e1e;
--border-color: #333;
--button-background: #6441a5;
--button-hover-background: #503682;
--button-shadow: rgba(0, 0, 0, 0.2);
--button-padding: 0.5rem 1rem;
--button-margin: 0.5rem 0;
--button-radius: 0.5rem;
--button-font-size: 0.875rem;
--input-background: #2a2a2a;
--input-border: #444;
--input-focus-border: #6441a5;
--input-padding: 0.5rem;
--input-radius: 0.5rem;
--form-gap: 1rem;
}
html {
max-width: 88ch;
padding: calc(1vmin + 0.5rem);
margin-inline: auto;
font-size: clamp(1em, 0.909em + 0.45vmin, 1.25em);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
}
a {
text-decoration: none;
color: inherit;
}
header {
padding: 10px 30px;
background: var(--header-background);
display: flex;
flex-direction: column;
align-items: flex-start;
}
ul {
list-style-type: none;
padding: 0;
}
.game {
margin-bottom: 1rem;
border: 1px solid var(--border-color);
}
img {
margin: 10px;
}
button {
background-color: var(--button-background);
color: white;
border: none;
padding: var(--button-padding);
margin: var(--button-margin);
border-radius: var(--button-radius);
cursor: pointer;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
font-size: var(--button-font-size);
box-shadow: 0 2px 4px var(--button-shadow);
}
button:hover {
background-color: var(--button-hover-background);
box-shadow: 0 3px 6px var(--button-shadow);
}
.navbar {
margin-bottom: 1rem;
text-align: center;
}
.logo {
text-align: center;
font-size: 2.5rem;
font-weight: 600;
margin: 0;
}
.messages {
list-style-type: none;
padding: 0;
margin-bottom: var(--form-gap);
}
.error {
color: red;
}
.success {
color: green;
}
form {
display: flex;
flex-direction: column;
gap: var(--form-gap);
background-color: var(--header-background);
padding: 1rem;
border: 1px solid var(--border-color);
}
form div {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 0.5rem;
font-weight: bold;
}
input, select, textarea {
background-color: var(--input-background);
border: 1px solid var(--input-border);
padding: var(--input-padding);
color: var(--text-color);
transition: border-color 0.3s ease;
}
input:focus, select:focus, textarea:focus {
border-color: var(--input-focus-border);
outline: none;
}
small {
color: #aaa;
margin-top: 0.5rem;
}
</style>
</head>
<body>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li {% if message.tags %}class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<h1 class="logo">
<a href="{% url 'core:index' %}">Twitch Drops</a>
</h1>
<div class="navbar">
<a href='{% url "api-1.0.0:openapi-view" %}'>API</a> |
<a href="https://github.com/sponsors/TheLovinator1">Donate</a> |
TheLovinator#9276
</div>
<h1>Add Discord Webhook</h1>
<form method="post">
{% extends "base.html" %}
{% block content %}
<div class="container">
<h1 class="my-4">Add Discord Webhook</h1>
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
{{ form.non_field_errors }}
<div>
<div class="mb-3">
{{ form.name.errors }}
<label for="{{ form.name.id_for_label }}">{{ form.name.label_tag }}</label>
{{ form.name }}
<small>{{ form.name.help_text }}</small>
<label for="{{ form.name.id_for_label }}" class="form-label">{{ form.name.label }}</label>
<input type="text"
name="name"
maxlength="255"
required=""
class="form-control"
aria-describedby="id_name_helptext"
id="id_name">
<div class="form-text text-muted">{{ form.name.help_text }}</div>
</div>
<div>
<div class="mb-3">
{{ form.webhook_url.errors }}
<label for="{{ form.webhook_url.id_for_label }}">{{ form.webhook_url.label_tag }}</label>
{{ form.webhook_url }}
<small>{{ form.webhook_url.help_text }}</small>
<label for="{{ form.webhook_url.id_for_label }}" class="form-label">{{ form.webhook_url.label }}</label>
<input type="url"
name="webhook_url"
required=""
class="form-control"
aria-describedby="id_webhook_url_helptext"
id="id_webhook_url">
<div class="form-text text-muted">{{ form.webhook_url.help_text }}</div>
</div>
<button type="submit">Add Webhook</button>
<button type="submit" class="btn btn-primary">Add Webhook</button>
</form>
<h2>Webhooks</h2>
<ul>
<h2 class="mt-5">Webhooks</h2>
<ul class="list-group mt-3">
{% for webhook in webhooks %}
<li>
<form method="post" action="{% url 'core:delete_discord_webhook' %}">
<strong>{{ webhook.name }}</strong>
<small>
<a href="{{ webhook.webhook_url }}">{{ webhook.webhook_url }}</a>
</small>
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<a href="{{ webhook.webhook_url }}" target="_blank">{{ webhook.name }}</a>
<small class="text-muted">added on {{ webhook.created_at|date:"F j, Y, g:i a" }}</small>
</div>
<form method="post"
action="{% url 'core:delete_discord_webhook' %}"
class="mb-0">
{% csrf_token %}
<input type="hidden" name="webhook_id" value="{{ webhook.id }}">
<input type="hidden" name="webhook_name" value="{{ webhook.name }}">
<input type="hidden" name="webhook_url" value="{{ webhook.webhook_url }}">
<button type="submit">Delete</button>
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
</li>
{% endfor %}
</ul>
</body>
</html>
</div>
{% endblock content %}

61
core/templates/base.html Normal file
View File

@@ -0,0 +1,61 @@
{% load static %}
{% load socialaccount %}
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Twitch drops">
<meta name="author" content="TheLovinator">
<meta name="keywords" content="Twitch, drops">
<meta name="robots" content="index, follow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Twitch drops</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
{% for message in messages %}
<div class="alert alert-dismissible {{ message.tags }} fade show"
role="alert">
<div>{{ message | safe }}</div>
<button type="button"
class="btn-close"
data-bs-dismiss="alert"
aria-label="Close"></button>
</div>
{% endfor %}
<article class="container mt-5">
<header class="d-flex justify-content-between align-items-center py-3 border-bottom">
<h1 class="h2">
<a href='{% url "core:index" %}' class="text-decoration-none">Twitch drops</a>
</h1>
<nav>
<ul class="nav">
<li class="nav-item">
<a class="nav-link" href='{% url "api-1.0.0:openapi-view" %}'>API</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/sponsors/TheLovinator1">Donate</a>
</li>
<li>
<a class="nav-link" href='{% url "core:add_discord_webhook" %}'>Webhooks</a>
</li>
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href='{% url "account_logout" %}'>Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href='{% provider_login_url "twitch" %}'>Login</a>
</li>
{% endif %}
</ul>
</nav>
</header>
{% block content %}
{% endblock content %}
</article>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</body>
</html>

View File

@@ -1,175 +1,69 @@
{% load socialaccount %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="description" content="Twitch Drops">
<meta name="author" content="TheLovinator">
<meta name="keywords" content="Twitch, Drops, Twitch Drops">
<meta name="robots" content="index, follow">
<title>Twitch Drops</title>
<style>
:root {
--background-color: #121212;
--text-color: #e0e0e0;
--header-background: #1e1e1e;
--border-color: #333;
--button-background: #6441a5;
--button-hover-background: #503682;
--button-shadow: rgba(0, 0, 0, 0.2);
--button-padding: 0.5rem 1rem;
--button-margin: 0.5rem 0;
--button-radius: 0.5rem;
--button-font-size: 0.875rem;
}
html {
max-width: 88ch;
padding: calc(1vmin + 0.5rem);
margin-inline: auto;
font-size: clamp(1em, 0.909em + 0.45vmin, 1.25em);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
}
a {
text-decoration: none;
color: inherit;
}
header {
padding: 10px 30px;
background: var(--header-background);
display: flex;
flex-direction: column;
align-items: flex-start;
}
ul {
list-style-type: none;
padding: 0;
}
.game {
margin-bottom: 1rem;
border: 1px solid var(--border-color);
}
img {
margin: 10px;
}
button {
background-color: var(--button-background);
color: white;
border: none;
padding: var(--button-padding);
margin: var(--button-margin);
border-radius: var(--button-radius);
cursor: pointer;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
font-size: var(--button-font-size);
box-shadow: 0 2px 4px var(--button-shadow);
}
button:hover {
background-color: var(--button-hover-background);
box-shadow: 0 3px 6px var(--button-shadow);
}
.navbar {
margin-bottom: 1rem;
text-align: center;
}
.logo {
text-align: center;
font-size: 2.5rem;
font-weight: 600;
margin: 0;
}
.messages {
list-style-type: none;
}
.error {
color: red;
}
.success {
color: green;
}
</style>
</head>
<body>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li {% if message.tags %}class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<h1 class="logo">Twitch Drops</h1>
<div class="navbar">
<a href='{% url "api-1.0.0:openapi-view" %}'>API</a> |
<a href="https://github.com/sponsors/TheLovinator1">Donate</a> |
TheLovinator#9276 |
{% if user.is_authenticated %}
<a href='{% url "account_logout" %}'>Logout</a>
{% else %}
<a href='{% provider_login_url "twitch" %}'>Login</a>
{% endif %}
</div>
{% extends "base.html" %}
{% block content %}
{% for organization, org_data in orgs_data.items %}
<ul>
<div class="container mt-4">
{% for game, game_data in org_data.games.items %}
<li class="game">
<header>
<div class="card mb-4">
<div class="row g-0">
<div class="col-md-2">
<img src="{{ game.image_url }}"
alt="{{ game.display_name }}"
class="img-fluid rounded-start"
height="100"
width="100"
loading="lazy">
<h2>
<a href="https://www.twitch.tv/directory/category/{{ game.slug }}">{{ game.display_name }}</a>
</div>
<div class="col-md-4">
<div class="card-body">
<h2 class="card-title h5">
<a href="https://www.twitch.tv/directory/category/{{ game.slug }}"
class="text-decoration-none">{{ game.display_name }}</a>
</h2>
<form action='{% url "core:subscription_create" %}' method="post">
</div>
</div>
<div class="col-md-6">
{% if discord_settings %}
<div class="card-body">
<form action='{% url "core:subscription_create" %}'
method="post"
class="mb-2">
{% csrf_token %}
<input type="hidden" name="game_id" value="{{ game.id }}">
<select name="discord_webhook">
<select name="discord_webhook" class="form-select">
{% for discord_setting in discord_settings %}
<option value="{{ discord_setting.id }}">{{ discord_setting.name }}</option>
{% endfor %}
</select>
<button type="submit">Subscribe to {{ game.display_name }}</button>
<div>
<a href="#" class="card-link mt-2">Notify when new drop are found</a>
</div>
<div>
<a href="#" class="card-link mt-2">Notify when the drop is live</a>
</div>
</form>
<form action='{% url "core:test" %}' method="post">
{% csrf_token %}
<input type="hidden"
name="org_id"
value="{{ org_data.drop_campaigns.0.pk }}">
<button type="submit">Test</button>
</form>
</header>
<ul>
</div>
{% else %}
<div class="card-body text-end">
<a href="{% url 'core:add_discord_webhook' %}" class="card-link">Add Discord settings</a>
</div>
{% endif %}
</div>
</div>
<ul class="list-group list-group-flush">
{% for drop_benefit in game_data.drop_benefits %}
<li>
<li class="list-group-item d-flex align-items-center">
<img src="{{ drop_benefit.image_asset_url }}"
alt="{{ drop_benefit.name }}"
height="100"
width="100"
class="img-fluid rounded me-3"
height="50"
width="50"
loading="lazy">
<a href="{{ drop_benefit.details_url }}">{{ drop_benefit.name }}</a>
<a href="{{ drop_benefit.details_url }}" class="text-decoration-none">{{ drop_benefit.name }}</a>
</li>
{% endfor %}
</ul>
</li>
</div>
{% endfor %}
</ul>
</div>
{% endfor %}
</body>
</html>
{% endblock content %}

6
static/css/bootstrap.min.css vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

7
static/css/bootstrap.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

61
static/css/style.css Normal file
View File

@@ -0,0 +1,61 @@
body {
background-color: #0c0c0c;
color: #ffffff;
}
a {
color: #ffffff;
}
a:hover {
color: #e91e63;
}
.nav-link {
color: #ffffff;
}
.nav-link:hover {
color: #e91e63;
}
.card {
background-color: #111111;
border: 1px solid #111111;
}
.card-title a {
color: #ffffff;
}
.card-title a:hover {
color: #e91e63;
}
.alert {
background-color: #333333;
border-color: #444444;
}
.list-group-item {
background-color: #141414;
border: 1px solid #141414;
}
.list-group-item a {
color: #ffffff;
}
.list-group-item a:hover {
color: #e91e63;
}
.btn-primary {
background-color: #06172e;
border-color: #000c1d;
}
.btn-primary:hover {
background-color: #040e1d;
border-color: #000c1d;
}