generated from JustScreaMy/ProjectTemplate
Compare commits
5 commits
10beabd148
...
2c64e447bc
Author | SHA1 | Date | |
---|---|---|---|
2c64e447bc | |||
![]() |
19210e1cc1 | ||
![]() |
6c28b0882b | ||
![]() |
7dffa93977 | ||
![]() |
04445a05a6 |
38 changed files with 1227 additions and 270 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,3 +3,6 @@ node_modules/
|
||||||
.idea/
|
.idea/
|
||||||
services/backend/db.sqlite3
|
services/backend/db.sqlite3
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
htmlcov/
|
||||||
|
media/
|
||||||
|
.coverage
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
1. `docker compose run --rm frontend npm install`
|
1. create .env from .env.example
|
||||||
2. `docker compose build`
|
2. `docker compose run --rm frontend npm install`
|
||||||
3. `docker compose up`
|
3. `docker compose build`
|
||||||
|
4. `docker compose up`
|
||||||
|
|
||||||
## Adding new frontend packages
|
## Adding new frontend packages
|
||||||
|
|
||||||
|
@ -13,3 +14,4 @@
|
||||||
## Adding new backend packages
|
## Adding new backend packages
|
||||||
|
|
||||||
`docker compose run --rm backend uv add <package>`
|
`docker compose run --rm backend uv add <package>`
|
||||||
|
`docker compose build` :)
|
||||||
|
|
10
TODO
10
TODO
|
@ -1,15 +1,13 @@
|
||||||
Nastylovat:
|
Nastylovat:
|
||||||
- footer
|
|
||||||
- lazyload načítat o něco dříve (neli zrušit alespoň mimo galerii)
|
- lazyload načítat o něco dříve (neli zrušit alespoň mimo galerii)
|
||||||
|
- mobilní verze
|
||||||
|
|
||||||
Naprogramovat:
|
Frontend:
|
||||||
- tlačítko "načíst další aktuality" v komponentě News (možná přidat i novou stránku /aktuality
|
|
||||||
- přidat v galerii zvětsovač na obrázky
|
- přidat v galerii zvětsovač na obrázky
|
||||||
- přidat volání na api
|
- notyfi
|
||||||
|
|
||||||
Naplánovat:
|
Naplánovat:
|
||||||
- kurzy + cena: vyskakovací okno na rezervaci s jménem lekce? jak to vyřešit s kalendářem? (stránka /rezervace)
|
- kurzy + cena: vyskakovací okno na rezervaci s jménem lekce? jak to vyřešit s kalendářem? (stránka /rezervace)
|
||||||
|
|
||||||
|
Backend:
|
||||||
DAVID:
|
|
||||||
- přidat posílání mailu panu vrchnímu
|
- přidat posílání mailu panu vrchnímu
|
|
@ -28,7 +28,11 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
SECRET_KEY = env.str('SECRET_KEY', 'django-insecure-x&7qy$na7*@u_4(izkfaz2yiea9+t(nf&#p9cnlq6x)_)jkacf')
|
SECRET_KEY = env.str('SECRET_KEY', 'django-insecure-x&7qy$na7*@u_4(izkfaz2yiea9+t(nf&#p9cnlq6x)_)jkacf')
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = False
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
"SHOW_TOOLBAR_CALLBACK": lambda request: False,
|
||||||
|
"IS_RUNNING_TESTS": False,
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_TOOLBAR_PANELS = [
|
DEBUG_TOOLBAR_PANELS = [
|
||||||
'debug_toolbar.panels.history.HistoryPanel',
|
'debug_toolbar.panels.history.HistoryPanel',
|
||||||
|
@ -72,6 +76,7 @@ INSTALLED_APPS = [
|
||||||
'tko.apps.TkoConfig',
|
'tko.apps.TkoConfig',
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'post_office',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -115,6 +120,7 @@ DATABASES = {
|
||||||
'default': env.db('DATABASE_URL')
|
'default': env.db('DATABASE_URL')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'post_office.EmailBackend'
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||||
|
@ -152,9 +158,9 @@ USE_TZ = True
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
# STATICFILES_DIRS = [
|
||||||
os.path.join(BASE_DIR, 'static')
|
# os.path.join(BASE_DIR, 'static')
|
||||||
]
|
# ]
|
||||||
|
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from tko.views import ContactView, NewArticleListView, AllArticleListView, EventListView
|
from tko.views import ContactView, NewArticleListView, AllArticleListView, EventListView, GalleryView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
@ -29,6 +29,7 @@ urlpatterns = [
|
||||||
path('load-articles/', NewArticleListView.as_view(), name='load-articles'),
|
path('load-articles/', NewArticleListView.as_view(), name='load-articles'),
|
||||||
path('load-all-articles/', AllArticleListView.as_view(), name='load-all-articles'),
|
path('load-all-articles/', AllArticleListView.as_view(), name='load-all-articles'),
|
||||||
path('load-events/', EventListView.as_view(), name='load-events'),
|
path('load-events/', EventListView.as_view(), name='load-events'),
|
||||||
|
path('load-gallery/', GalleryView.as_view(), name='load-gallery'),
|
||||||
] + debug_toolbar_urls()
|
] + debug_toolbar_urls()
|
||||||
|
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
After Width: | Height: | Size: 234 KiB |
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
|
@ -4,7 +4,8 @@ version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Jakub Kropáček", email = "kropikuba@gmail.com"}
|
{name = "Jakub Kropáček", email = "kropikuba@gmail.com"},
|
||||||
|
{name = "Nikola Kubeczková", email = "kubeczkova.n@gmail.com"}
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"django>=5.1.5",
|
"django>=5.1.5",
|
||||||
|
@ -15,6 +16,10 @@ dependencies = [
|
||||||
"django-environ>=0.12.0",
|
"django-environ>=0.12.0",
|
||||||
"wait-for-it>=2.3.0",
|
"wait-for-it>=2.3.0",
|
||||||
"django-cors-headers>=4.7.0",
|
"django-cors-headers>=4.7.0",
|
||||||
|
"django-post-office>=3.9.1",
|
||||||
|
"pytest-django>=4.10.0",
|
||||||
|
"factory-boy>=3.3.3",
|
||||||
|
"pytest-cov>=6.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
|
@ -23,3 +28,6 @@ package = false
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
DJANGO_SETTINGS_MODULE = "backend.settings"
|
||||||
|
|
|
@ -19,9 +19,15 @@ class ContactAdmin(admin.ModelAdmin):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleImageInline(admin.TabularInline): # Or admin.StackedInline for a different layout
|
||||||
|
model = models.ArticleImage
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Article)
|
@admin.register(models.Article)
|
||||||
class ArticleAdmin(admin.ModelAdmin):
|
class ArticleAdmin(admin.ModelAdmin):
|
||||||
list_display = ['title', 'date', 'author']
|
list_display = ['title', 'date', 'author']
|
||||||
|
inlines = [ArticleImageInline]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Event)
|
@admin.register(models.Event)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-03-05 15:36
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tko', '0004_alter_article_image'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='article',
|
||||||
|
name='image',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(auto_now_add=True),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ArticleImage',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('image', models.FileField(default='default.png', upload_to='images/%Y')),
|
||||||
|
('is_main', models.BooleanField(default=False)),
|
||||||
|
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='tko.article')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-03-10 16:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tko', '0005_remove_article_image_alter_article_date_articleimage'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='articleimage',
|
||||||
|
name='is_main',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='image',
|
||||||
|
field=models.FileField(blank=True, default='default.png', upload_to='images/%Y'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='articleimage',
|
||||||
|
name='image',
|
||||||
|
field=models.FileField(upload_to='images/%Y'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-03-10 18:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tko', '0006_remove_articleimage_is_main_article_image_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='article',
|
||||||
|
name='image',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='articleimage',
|
||||||
|
name='main',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='articleimage',
|
||||||
|
name='image',
|
||||||
|
field=models.FileField(default='default.png', upload_to='images/%Y'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,6 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
class Contact(models.Model):
|
class Contact(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
|
@ -14,16 +14,23 @@ class Contact(models.Model):
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
title = models.CharField(max_length=100)
|
title = models.CharField(max_length=100)
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
image = models.FileField(default="default.png", blank=True)
|
|
||||||
date = models.DateField(auto_now_add=True)
|
date = models.DateField(auto_now_add=True)
|
||||||
author = models.CharField(max_length=100)
|
author = models.CharField(max_length=100)
|
||||||
|
|
||||||
active_to = models.DateField(null=True, blank=True) # do not show some invitation after this date
|
active_to = models.DateField(null=True, blank=True) # do not show some invitation after this date
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleImage(models.Model):
|
||||||
|
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='images')
|
||||||
|
image = models.FileField(upload_to="images/%Y", default="default.png")
|
||||||
|
main = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Image for {self.article.title}, {self.article.date}"
|
||||||
|
|
||||||
|
|
||||||
class Event(models.Model):
|
class Event(models.Model):
|
||||||
title = models.CharField(max_length=100)
|
title = models.CharField(max_length=100)
|
||||||
start_date = models.DateTimeField()
|
start_date = models.DateTimeField()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.timezone import localtime
|
from django.utils.timezone import localtime
|
||||||
|
|
||||||
from tko.models import Contact, Article, Event
|
from tko.models import Contact, Article, Event, ArticleImage
|
||||||
|
|
||||||
|
|
||||||
class ContactSerializer(serializers.ModelSerializer):
|
class ContactSerializer(serializers.ModelSerializer):
|
||||||
|
@ -10,17 +10,36 @@ class ContactSerializer(serializers.ModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleImageSerializer(serializers.ModelSerializer):
|
||||||
|
title = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ArticleImage
|
||||||
|
fields = ['image', 'title']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_title(obj):
|
||||||
|
return obj.article.title
|
||||||
|
|
||||||
|
|
||||||
class ArticleListSerializer(serializers.ModelSerializer):
|
class ArticleListSerializer(serializers.ModelSerializer):
|
||||||
date = serializers.SerializerMethodField()
|
date = serializers.SerializerMethodField()
|
||||||
|
image = serializers.SerializerMethodField()
|
||||||
|
images = ArticleImageSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Article
|
model = Article
|
||||||
fields = ["id", "author", "content", "date", "image", "title"]
|
fields = ["id", "author", "content", "date", "image", "title", "images"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_date(obj):
|
def get_date(obj):
|
||||||
return obj.date.strftime("%-d. %-m. %Y")
|
return obj.date.strftime("%-d. %-m. %Y")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_image(obj):
|
||||||
|
main_image = obj.images.order_by("main").first()
|
||||||
|
return main_image.image.url if main_image else None
|
||||||
|
|
||||||
|
|
||||||
class EventListSerializer(serializers.ModelSerializer):
|
class EventListSerializer(serializers.ModelSerializer):
|
||||||
start = serializers.SerializerMethodField()
|
start = serializers.SerializerMethodField()
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
0
services/backend/tko/tests/__init__.py
Normal file
0
services/backend/tko/tests/__init__.py
Normal file
62
services/backend/tko/tests/factories.py
Normal file
62
services/backend/tko/tests/factories.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
import factory
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from factory.fuzzy import FuzzyDate
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from tko.models import Article, Event, ArticleImage, Contact
|
||||||
|
|
||||||
|
|
||||||
|
class ContactFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Contact
|
||||||
|
|
||||||
|
name = factory.Faker("name")
|
||||||
|
email = factory.Faker("email")
|
||||||
|
phone_number = factory.Faker("phone_number")
|
||||||
|
content = factory.Faker("text")
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Article
|
||||||
|
|
||||||
|
title = factory.Faker("name")
|
||||||
|
content = factory.Faker("text")
|
||||||
|
date = FuzzyDate(datetime.date(2021, 1, 1), date.today())
|
||||||
|
author = factory.Faker("name")
|
||||||
|
active_to = FuzzyDate(datetime.date(2021, 1, 1), datetime.date(2025, 1, 1))
|
||||||
|
|
||||||
|
|
||||||
|
class GalleryFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = ArticleImage
|
||||||
|
|
||||||
|
article = factory.SubFactory(ArticleFactory)
|
||||||
|
image = factory.django.FileField(filename='image.png')
|
||||||
|
main = factory.Faker("boolean")
|
||||||
|
|
||||||
|
|
||||||
|
class EventFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Event
|
||||||
|
|
||||||
|
title = factory.Faker("name")
|
||||||
|
start_date = FuzzyDate(datetime.date(2021, 1, 1), datetime.date(2025, 1, 1))
|
||||||
|
end_date = factory.LazyAttribute(
|
||||||
|
lambda obj: obj.start_date + datetime.timedelta(minutes=randint(5, 5 * 24 * 60))
|
||||||
|
)
|
||||||
|
color = factory.Faker("color")
|
||||||
|
|
||||||
|
|
||||||
|
class UserFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
|
||||||
|
username = factory.Faker("name")
|
||||||
|
email = factory.Faker("email")
|
||||||
|
password = "password"
|
||||||
|
is_superuser = True
|
37
services/backend/tko/tests/test_admin.py
Normal file
37
services/backend/tko/tests/test_admin.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import pytest
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from tko.admin import ContactAdmin
|
||||||
|
from tko.models import Contact
|
||||||
|
from tko.tests.factories import UserFactory
|
||||||
|
|
||||||
|
from django.contrib.admin.sites import site
|
||||||
|
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
@pytest.mark.django_db(transaction=True)
|
||||||
|
class TestAdmin:
|
||||||
|
url = reverse("admin:index")
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
def test_has_add_permission(self):
|
||||||
|
user = UserFactory()
|
||||||
|
admin = ContactAdmin(Contact, site)
|
||||||
|
request = self.client.get(self.url)
|
||||||
|
request.user = user
|
||||||
|
assert admin.has_add_permission(request) is False
|
||||||
|
|
||||||
|
def test_has_delete_permission(self):
|
||||||
|
user = UserFactory()
|
||||||
|
admin = ContactAdmin(Contact, site)
|
||||||
|
request = self.client.get(self.url)
|
||||||
|
request.user = user
|
||||||
|
assert admin.has_delete_permission(request) is False
|
||||||
|
|
||||||
|
def test_has_change_permission(self):
|
||||||
|
user = UserFactory()
|
||||||
|
admin = ContactAdmin(Contact, site)
|
||||||
|
request = self.client.get(self.url)
|
||||||
|
request.user = user
|
||||||
|
assert admin.has_change_permission(request) is False
|
53
services/backend/tko/tests/test_article.py
Normal file
53
services/backend/tko/tests/test_article.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import pytest
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from tko.tests.factories import ArticleFactory
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db(transaction=True)
|
||||||
|
class TestLoadArticle:
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_article(old, new):
|
||||||
|
for _ in range(new): ArticleFactory(active_to=None)
|
||||||
|
for _ in range(old): ArticleFactory()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("articles", "length", "result"),
|
||||||
|
[
|
||||||
|
((2, 5), 2, 200),
|
||||||
|
((2, 1), 1, 200),
|
||||||
|
((5, 0), 0, 200),
|
||||||
|
((3, 1), 1, 200),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_load_articles(self, articles, length, result):
|
||||||
|
self.create_article(*articles)
|
||||||
|
response = self.client.get(reverse("load-articles"))
|
||||||
|
assert response.status_code == result
|
||||||
|
assert len(response.data) == length
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("articles", "length", "result"),
|
||||||
|
[
|
||||||
|
((2, 5), 5, 200),
|
||||||
|
((2, 1), 1, 200),
|
||||||
|
((5, 0), 0, 200),
|
||||||
|
((3, 1), 1, 200),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_load_all_articles(self, articles, length, result):
|
||||||
|
self.create_article(*articles)
|
||||||
|
response = self.client.get(
|
||||||
|
path=reverse("load-all-articles")
|
||||||
|
)
|
||||||
|
assert response.status_code == result
|
||||||
|
assert len(response.data) == length
|
||||||
|
|
||||||
|
|
||||||
|
def test_str_article(self):
|
||||||
|
title = "Test_article"
|
||||||
|
assert str(ArticleFactory(title=title)) == title
|
40
services/backend/tko/tests/test_contact.py
Normal file
40
services/backend/tko/tests/test_contact.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import pytest
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from tko.tests.factories import ContactFactory
|
||||||
|
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
@pytest.mark.django_db(transaction=True)
|
||||||
|
class TestContact:
|
||||||
|
url = reverse("create-contact")
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("name", "email", "phone", "message", "result"),
|
||||||
|
[
|
||||||
|
("name", "email@tko.cz", "770707505", "message", 200),
|
||||||
|
("name", "email@tko.cz", "", "message", 400),
|
||||||
|
("name", "", "770707505", "message", 400),
|
||||||
|
("name", "", "", "message", 400),
|
||||||
|
("name ahoj", "email@tko.cz", "+420770707505", "message skdo nsdkl skd sakdksd", 200),
|
||||||
|
("", "email", "770707505", "message", 400),
|
||||||
|
])
|
||||||
|
def test_create_contact(self, name, email, phone, message, result):
|
||||||
|
response = self.client.post(
|
||||||
|
path=self.url,
|
||||||
|
data={
|
||||||
|
"name": name,
|
||||||
|
"email": email,
|
||||||
|
"phone_number": phone,
|
||||||
|
"content": message,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert response.status_code == result
|
||||||
|
if result == 200:
|
||||||
|
assert str(ContactFactory(
|
||||||
|
name=name,
|
||||||
|
email=email,
|
||||||
|
phone_number=phone,
|
||||||
|
)) == f"{name}, {email}, {phone}"
|
36
services/backend/tko/tests/test_event.py
Normal file
36
services/backend/tko/tests/test_event.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import pytest
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from tko.tests.factories import EventFactory
|
||||||
|
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
@pytest.mark.django_db(transaction=True)
|
||||||
|
class TestEvent:
|
||||||
|
url = reverse("load-events")
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_events(length):
|
||||||
|
EventFactory.create_batch(size=length)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("length", "result"),
|
||||||
|
[
|
||||||
|
(2, 200),
|
||||||
|
(6, 200),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_load_events(self, length, result):
|
||||||
|
self.create_events(length)
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
assert response.status_code == result
|
||||||
|
assert len(response.data) == length
|
||||||
|
|
||||||
|
|
||||||
|
def test_str_article_image(self):
|
||||||
|
title = "Test_event"
|
||||||
|
assert str(
|
||||||
|
EventFactory(title=title)
|
||||||
|
) == title
|
65
services/backend/tko/tests/test_gallery.py
Normal file
65
services/backend/tko/tests/test_gallery.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from tko.tests.factories import GalleryFactory, ArticleFactory
|
||||||
|
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
@pytest.mark.django_db(transaction=True)
|
||||||
|
class TestGallery:
|
||||||
|
url = reverse("load-gallery")
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_images(old, new):
|
||||||
|
for _ in range(new):
|
||||||
|
article = ArticleFactory(active_to=None)
|
||||||
|
for _ in range(new): GalleryFactory(article=article)
|
||||||
|
for _ in range(old):
|
||||||
|
article = ArticleFactory()
|
||||||
|
for _ in range(old): GalleryFactory(article=article)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("gallery", "length", "result"),
|
||||||
|
[
|
||||||
|
((2, 5), 29, 200),
|
||||||
|
((2, 1), 5, 200),
|
||||||
|
((4, 2), 20, 200),
|
||||||
|
((3, 1), 10, 200),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_load_gallery(self, gallery, length, result):
|
||||||
|
self.create_images(*gallery)
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
assert response.status_code == result
|
||||||
|
assert len(response.data) == length
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("gallery", "length", "result"),
|
||||||
|
[
|
||||||
|
((2, 5), 5, 200),
|
||||||
|
((2, 1), 1, 200),
|
||||||
|
((4, 0), 0, 200),
|
||||||
|
((3, 1), 1, 200),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_load_article_images(self, gallery, length, result):
|
||||||
|
old, new = gallery
|
||||||
|
self.create_images(*gallery)
|
||||||
|
response = self.client.get(reverse("load-articles"))
|
||||||
|
for article in response.data:
|
||||||
|
assert len(article["images"]) == new
|
||||||
|
assert response.status_code == result
|
||||||
|
|
||||||
|
|
||||||
|
def test_str_article_image(self):
|
||||||
|
title = "Test_article"
|
||||||
|
assert str(
|
||||||
|
GalleryFactory(article=ArticleFactory(title=title))
|
||||||
|
) == f"Image for {title}, {datetime.date.today()}"
|
|
@ -1,21 +1,37 @@
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from rest_framework.generics import ListAPIView, CreateAPIView
|
from post_office import mail
|
||||||
from rest_framework import permissions
|
|
||||||
|
|
||||||
from tko.models import Article, Event
|
from rest_framework.generics import ListAPIView, CreateAPIView
|
||||||
from tko.serializers import ArticleListSerializer, EventListSerializer, ContactSerializer
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from tko.models import Article, Event, ArticleImage
|
||||||
|
from tko.serializers import ArticleListSerializer, EventListSerializer, ContactSerializer, ArticleImageSerializer
|
||||||
|
|
||||||
|
|
||||||
class ContactView(CreateAPIView):
|
class ContactView(CreateAPIView):
|
||||||
serializer_class = ContactSerializer
|
serializer_class = ContactSerializer
|
||||||
permission_classes = [permissions.AllowAny]
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
|
||||||
|
mail.send(
|
||||||
|
recipients="kubeczkova.n@gmail.com",
|
||||||
|
subject="Zpráva z webu TKO",
|
||||||
|
message=f"Jméno: {serializer.data['name']}\n"
|
||||||
|
f"Email: {serializer.data['email']}\n"
|
||||||
|
f"Telefón: {serializer.data['phone_number']}\n\n"
|
||||||
|
f"Zpráva: {serializer.data['content']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class NewArticleListView(ListAPIView):
|
class NewArticleListView(ListAPIView):
|
||||||
serializer_class = ArticleListSerializer
|
serializer_class = ArticleListSerializer
|
||||||
permission_classes = [permissions.AllowAny]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Article.objects.filter(
|
return Article.objects.filter(
|
||||||
|
@ -25,7 +41,6 @@ class NewArticleListView(ListAPIView):
|
||||||
|
|
||||||
class AllArticleListView(ListAPIView):
|
class AllArticleListView(ListAPIView):
|
||||||
serializer_class = ArticleListSerializer
|
serializer_class = ArticleListSerializer
|
||||||
permission_classes = [permissions.AllowAny]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Article.objects.filter(
|
return Article.objects.filter(
|
||||||
|
@ -33,7 +48,11 @@ class AllArticleListView(ListAPIView):
|
||||||
).order_by('-date')
|
).order_by('-date')
|
||||||
|
|
||||||
|
|
||||||
|
class GalleryView(ListAPIView):
|
||||||
|
queryset = ArticleImage.objects.all().order_by("-article_id", "main")
|
||||||
|
serializer_class = ArticleImageSerializer
|
||||||
|
|
||||||
|
|
||||||
class EventListView(ListAPIView):
|
class EventListView(ListAPIView):
|
||||||
queryset = Event.objects.all()
|
queryset = Event.objects.all()
|
||||||
serializer_class = EventListSerializer
|
serializer_class = EventListSerializer
|
||||||
permission_classes = [permissions.AllowAny]
|
|
||||||
|
|
|
@ -34,8 +34,12 @@ dependencies = [
|
||||||
{ name = "django-cors-headers" },
|
{ name = "django-cors-headers" },
|
||||||
{ name = "django-debug-toolbar" },
|
{ name = "django-debug-toolbar" },
|
||||||
{ name = "django-environ" },
|
{ name = "django-environ" },
|
||||||
|
{ name = "django-post-office" },
|
||||||
{ name = "djangorestframework" },
|
{ name = "djangorestframework" },
|
||||||
|
{ name = "factory-boy" },
|
||||||
{ name = "psycopg2-binary" },
|
{ name = "psycopg2-binary" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "pytest-django" },
|
||||||
{ name = "uvicorn", extra = ["standard"] },
|
{ name = "uvicorn", extra = ["standard"] },
|
||||||
{ name = "wait-for-it" },
|
{ name = "wait-for-it" },
|
||||||
]
|
]
|
||||||
|
@ -46,12 +50,33 @@ requires-dist = [
|
||||||
{ name = "django-cors-headers", specifier = ">=4.7.0" },
|
{ name = "django-cors-headers", specifier = ">=4.7.0" },
|
||||||
{ name = "django-debug-toolbar", specifier = ">=5.0.1" },
|
{ name = "django-debug-toolbar", specifier = ">=5.0.1" },
|
||||||
{ name = "django-environ", specifier = ">=0.12.0" },
|
{ name = "django-environ", specifier = ">=0.12.0" },
|
||||||
|
{ name = "django-post-office", specifier = ">=3.9.1" },
|
||||||
{ name = "djangorestframework", specifier = ">=3.15.2" },
|
{ name = "djangorestframework", specifier = ">=3.15.2" },
|
||||||
|
{ name = "factory-boy", specifier = ">=3.3.3" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.10" },
|
{ name = "psycopg2-binary", specifier = ">=2.9.10" },
|
||||||
|
{ name = "pytest-cov", specifier = ">=6.0.0" },
|
||||||
|
{ name = "pytest-django", specifier = ">=4.10.0" },
|
||||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" },
|
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" },
|
||||||
{ name = "wait-for-it", specifier = ">=2.3.0" },
|
{ name = "wait-for-it", specifier = ">=2.3.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bleach"
|
||||||
|
version = "6.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "webencodings" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
css = [
|
||||||
|
{ name = "tinycss2" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.1.8"
|
version = "8.1.8"
|
||||||
|
@ -73,6 +98,45 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.7.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6b/bf/3effb7453498de9c14a81ca21e1f92e6723ce7ebdc5402ae30e4dcc490ac/coverage-7.7.1.tar.gz", hash = "sha256:199a1272e642266b90c9f40dec7fd3d307b51bf639fa0d15980dc0b3246c1393", size = 810332 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/b0/4eaba302a86ec3528231d7cfc954ae1929ec5d42b032eb6f5b5f5a9155d2/coverage-7.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eff187177d8016ff6addf789dcc421c3db0d014e4946c1cc3fbf697f7852459d", size = 211253 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/68/21b973e6780a3f2457e31ede1aca6c2f84bda4359457b40da3ae805dcf30/coverage-7.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2444fbe1ba1889e0b29eb4d11931afa88f92dc507b7248f45be372775b3cef4f", size = 211504 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/b4/c19e9c565407664390254252496292f1e3076c31c5c01701ffacc060e745/coverage-7.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:177d837339883c541f8524683e227adcaea581eca6bb33823a2a1fdae4c988e1", size = 245566 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/0e/f9829cdd25e5083638559c8c267ff0577c6bab19dacb1a4fcfc1e70e41c0/coverage-7.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15d54ecef1582b1d3ec6049b20d3c1a07d5e7f85335d8a3b617c9960b4f807e0", size = 242455 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/57/a3ada2e50a665bf6d9851b5eb3a9a07d7e38f970bdd4d39895f311331d56/coverage-7.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c82b27c56478d5e1391f2e7b2e7f588d093157fa40d53fd9453a471b1191f2", size = 244713 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/d3/f15c7d45682a73eca0611427896016bad4c8f635b0fc13aae13a01f8ed9d/coverage-7.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:315ff74b585110ac3b7ab631e89e769d294f303c6d21302a816b3554ed4c81af", size = 244476 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/3b/64540074e256082b220e8810fd72543eff03286c59dc91976281dc0a559c/coverage-7.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4dd532dac197d68c478480edde74fd4476c6823355987fd31d01ad9aa1e5fb59", size = 242695 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/c1/9cad25372ead7f9395a91bb42d8ae63e6cefe7408eb79fd38797e2b763eb/coverage-7.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:385618003e3d608001676bb35dc67ae3ad44c75c0395d8de5780af7bb35be6b2", size = 243888 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/c6/c3e6c895bc5b95ccfe4cb5838669dbe5226ee4ad10604c46b778c304d6f9/coverage-7.7.1-cp312-cp312-win32.whl", hash = "sha256:63306486fcb5a827449464f6211d2991f01dfa2965976018c9bab9d5e45a35c8", size = 213744 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/8a/6df2fcb4c3e38ec6cd7e211ca8391405ada4e3b1295695d00aa07c6ee736/coverage-7.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:37351dc8123c154fa05b7579fdb126b9f8b1cf42fd6f79ddf19121b7bdd4aa04", size = 214546 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/2a/1a254eaadb01c163b29d6ce742aa380fc5cfe74a82138ce6eb944c42effa/coverage-7.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eebd927b86761a7068a06d3699fd6c20129becf15bb44282db085921ea0f1585", size = 211277 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/00/9636028365efd4eb6db71cdd01d99e59f25cf0d47a59943dbee32dd1573b/coverage-7.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a79c4a09765d18311c35975ad2eb1ac613c0401afdd9cb1ca4110aeb5dd3c4c", size = 211551 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/c8/14aed97f80363f055b6cd91e62986492d9fe3b55e06b4b5c82627ae18744/coverage-7.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1c65a739447c5ddce5b96c0a388fd82e4bbdff7251396a70182b1d83631019", size = 245068 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/76/9c5fe3f900e01d7995b0cda08fc8bf9773b4b1be58bdd626f319c7d4ec11/coverage-7.7.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392cc8fd2b1b010ca36840735e2a526fcbd76795a5d44006065e79868cc76ccf", size = 242109 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/81/760993bb536fb674d3a059f718145dcd409ed6d00ae4e3cbf380019fdfd0/coverage-7.7.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bb47cc9f07a59a451361a850cb06d20633e77a9118d05fd0f77b1864439461b", size = 244129 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/be/1114a19f93eae0b6cd955dabb5bee80397bd420d846e63cd0ebffc134e3d/coverage-7.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b4c144c129343416a49378e05c9451c34aae5ccf00221e4fa4f487db0816ee2f", size = 244201 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/8d/9128fd283c660474c7dc2b1ea5c66761bc776b970c1724989ed70e9d6eee/coverage-7.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bc96441c9d9ca12a790b5ae17d2fa6654da4b3962ea15e0eabb1b1caed094777", size = 242282 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/2a/6d7dbfe9c1f82e2cdc28d48f4a0c93190cf58f057fa91ba2391b92437fe6/coverage-7.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d03287eb03186256999539d98818c425c33546ab4901028c8fa933b62c35c3a", size = 243570 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/3e/29f1e4ce3bb951bcf74b2037a82d94c5064b3334304a3809a95805628838/coverage-7.7.1-cp313-cp313-win32.whl", hash = "sha256:8fed429c26b99641dc1f3a79179860122b22745dd9af36f29b141e178925070a", size = 213772 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/3a/cf029bf34aefd22ad34f0e808eba8d5830f297a1acb483a2124f097ff769/coverage-7.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:092b134129a8bb940c08b2d9ceb4459af5fb3faea77888af63182e17d89e1cf1", size = 214575 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/4c/fb8b35f186a2519126209dce91ab8644c9a901cf04f8dfa65576ca2dd9e8/coverage-7.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3154b369141c3169b8133973ac00f63fcf8d6dbcc297d788d36afbb7811e511", size = 212113 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/90/e834ffc86fd811c5b570a64ee1895b20404a247ec18a896b9ba543b12097/coverage-7.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:264ff2bcce27a7f455b64ac0dfe097680b65d9a1a293ef902675fa8158d20b24", size = 212333 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/a1/27f0ad39569b3b02410b881c42e58ab403df13fcd465b475db514b83d3d3/coverage-7.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba8480ebe401c2f094d10a8c4209b800a9b77215b6c796d16b6ecdf665048950", size = 256566 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/3b/21fa66a1db1b90a0633e771a32754f7c02d60236a251afb1b86d7e15d83a/coverage-7.7.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:520af84febb6bb54453e7fbb730afa58c7178fd018c398a8fcd8e269a79bf96d", size = 252276 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/e5/4ab83a59b0f8ac4f0029018559fc4c7d042e1b4552a722e2bfb04f652296/coverage-7.7.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d96127ae01ff571d465d4b0be25c123789cef88ba0879194d673fdea52f54e", size = 254616 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/7a/4224417c0ccdb16a5ba4d8d1fcfaa18439be1624c29435bb9bc88ccabdfb/coverage-7.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0ce92c5a9d7007d838456f4b77ea159cb628187a137e1895331e530973dcf862", size = 255707 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/20/ff18a329ccaa3d035e2134ecf3a2e92a52d3be6704c76e74ca5589ece260/coverage-7.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0dab4ef76d7b14f432057fdb7a0477e8bffca0ad39ace308be6e74864e632271", size = 253876 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/e8/1d6f1a6651672c64f45ffad05306dad9c4c189bec694270822508049b2cb/coverage-7.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7e688010581dbac9cab72800e9076e16f7cccd0d89af5785b70daa11174e94de", size = 254687 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/ea/1b9a14cf3e2bc3fd9de23a336a8082091711c5f480b500782d59e84a8fe5/coverage-7.7.1-cp313-cp313t-win32.whl", hash = "sha256:e52eb31ae3afacdacfe50705a15b75ded67935770c460d88c215a9c0c40d0e9c", size = 214486 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/bb/faa6bcf769cb7b3b660532a30d77c440289b40636c7f80e498b961295d07/coverage-7.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a6b6b3bd121ee2ec4bd35039319f3423d0be282b9752a5ae9f18724bc93ebe7c", size = 215647 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/26/9f53293ff4cc1d47d98367ce045ca2e62746d6be74a5c6851a474eabf59b/coverage-7.7.1-py3-none-any.whl", hash = "sha256:822fa99dd1ac686061e1219b67868e25d9757989cf2259f735a4802497d6da31", size = 203006 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django"
|
name = "django"
|
||||||
version = "5.1.5"
|
version = "5.1.5"
|
||||||
|
@ -122,6 +186,19 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957 },
|
{ url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-post-office"
|
||||||
|
version = "3.9.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "bleach", extra = ["css"] },
|
||||||
|
{ name = "django" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/de/e9/7369fb453269ded8d2fff58299fe653033702bd15a427bf35353d4606df3/django-post_office-3.9.1.tar.gz", hash = "sha256:3d8f502f7829e4cf83e830d9efd6909bb44690af6bc41c7e4fc5a85d7b04df10", size = 76624 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/12/19fdf17fb1a0d5e4418798cf9ed66b8aee39da564e875ec025627e08abdf/django_post_office-3.9.1-py3-none-any.whl", hash = "sha256:0082c4dd17854f66077ef457cf868c9bc5b6247de99d5755c45ddea1c518ed6f", size = 85671 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "djangorestframework"
|
name = "djangorestframework"
|
||||||
version = "3.15.2"
|
version = "3.15.2"
|
||||||
|
@ -134,6 +211,30 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/b6/fa99d8f05eff3a9310286ae84c4059b08c301ae4ab33ae32e46e8ef76491/djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20", size = 1071235 },
|
{ url = "https://files.pythonhosted.org/packages/7c/b6/fa99d8f05eff3a9310286ae84c4059b08c301ae4ab33ae32e46e8ef76491/djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20", size = 1071235 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "factory-boy"
|
||||||
|
version = "3.3.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "faker" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ba/98/75cacae9945f67cfe323829fc2ac451f64517a8a330b572a06a323997065/factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03", size = 164146 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/8d/2bc5f5546ff2ccb3f7de06742853483ab75bf74f36a92254702f8baecc79/factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc", size = 37036 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "faker"
|
||||||
|
version = "37.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "tzdata" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ba/a6/b77f42021308ec8b134502343da882c0905d725a4d661c7adeaf7acaf515/faker-37.1.0.tar.gz", hash = "sha256:ad9dc66a3b84888b837ca729e85299a96b58fdaef0323ed0baace93c9614af06", size = 1875707 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/a1/8936bc8e79af80ca38288dd93ed44ed1f9d63beb25447a4c59e746e01f8d/faker-37.1.0-py3-none-any.whl", hash = "sha256:dc2f730be71cb770e9c715b13374d80dbcee879675121ab51f9683d262ae9a1c", size = 1918783 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -174,6 +275,33 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "24.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg2-binary"
|
name = "psycopg2-binary"
|
||||||
version = "2.9.10"
|
version = "2.9.10"
|
||||||
|
@ -205,6 +333,46 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 },
|
{ url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "8.3.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "coverage" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-django"
|
||||||
|
version = "4.10.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a5/10/a096573b4b896f18a8390d9dafaffc054c1f613c60bf838300732e538890/pytest_django-4.10.0.tar.gz", hash = "sha256:1091b20ea1491fd04a310fc9aaff4c01b4e8450e3b157687625e16a6b5f3a366", size = 84710 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/4c/a4fe18205926216e1aebe1f125cba5bce444f91b6e4de4f49fa87e322775/pytest_django-4.10.0-py3-none-any.whl", hash = "sha256:57c74ef3aa9d89cae5a5d73fbb69a720a62673ade7ff13b9491872409a3f5918", size = 23975 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -258,6 +426,18 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 },
|
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinycss2"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "webencodings" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.12.2"
|
version = "4.12.2"
|
||||||
|
@ -368,6 +548,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 },
|
{ url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webencodings"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "websockets"
|
name = "websockets"
|
||||||
version = "14.2"
|
version = "14.2"
|
||||||
|
|
|
@ -20,7 +20,7 @@ h2 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
|
@ -46,6 +46,17 @@ h5 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem; /* Smaller size for mobile */
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.2rem; /* Slightly smaller h2 for mobile */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sheet__box {
|
.sheet__box {
|
||||||
min-width: 25rem;
|
min-width: 25rem;
|
||||||
margin-left: calc(20% - 2.5rem);
|
margin-left: calc(20% - 2.5rem);
|
||||||
|
@ -95,12 +106,33 @@ h5 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact__dialog__title {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.contact__button {
|
.contact__button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #CF3476;
|
background-color: #CF3476;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.v-dialog > .v-card {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact__dialog__title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.articles {
|
.articles {
|
||||||
margin-left: calc(20% - 2.5rem);
|
margin-left: calc(20% - 2.5rem);
|
||||||
margin-right: calc(20% - 2.5rem);
|
margin-right: calc(20% - 2.5rem);
|
||||||
|
@ -118,6 +150,12 @@ h5 {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.carousel__image img {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
object-fit: contain !important;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.article__title {
|
.article__title {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
@ -164,35 +202,70 @@ h5 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing {
|
.pricing {
|
||||||
margin-left: calc(20% - 2.5rem);
|
margin-left: auto;
|
||||||
margin-right: calc(20% - 2.5rem);
|
margin-right: auto;
|
||||||
|
max-width: 1500px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing-box {
|
.pricing-box {
|
||||||
padding: 1rem;
|
padding: 1.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
min-width: 25rem;
|
min-width: 18rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
height: 230px;
|
height: 100%;
|
||||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing-box:hover {
|
.pricing-box:hover {
|
||||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.8);
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-title {
|
||||||
|
min-height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-desc {
|
||||||
|
min-height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing__price {
|
.pricing__price {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 3.5rem;
|
font-size: 3rem;
|
||||||
|
font-weight: bold;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
text-align: center;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing__subtitle {
|
.pricing__subtitle {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: #555;
|
||||||
text-align: center;
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.pricing-box {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-title,
|
||||||
|
.pricing-desc {
|
||||||
|
min-height: unset; /* Removes forced height to prevent breakage */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.trainers__parallax {
|
.trainers__parallax {
|
||||||
|
@ -201,12 +274,45 @@ h5 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.trainers {
|
.trainers {
|
||||||
min-width: 25rem;
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 1500px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-left: calc(20% - 2.5rem);
|
text-align: center;
|
||||||
margin-right: calc(20% - 2.5rem);
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainer-avatar {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Styles */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.trainers__parallax {
|
||||||
|
max-height: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainers {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainer-avatar {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.advantage {
|
.advantage {
|
||||||
|
@ -221,6 +327,7 @@ h5 {
|
||||||
|
|
||||||
.advantage__title {
|
.advantage__title {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
|
padding: 0.5rem 0;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -229,17 +336,37 @@ h5 {
|
||||||
|
|
||||||
.advantage__text {
|
.advantage__text {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
|
padding-bottom: 1rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.advantage {
|
||||||
|
min-width: 100%;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.advantage__title {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advantage__text {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.about {
|
.about {
|
||||||
min-width: 25rem;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-left: calc(20% - 2.5rem);
|
max-width: 80%;
|
||||||
margin-right: calc(20% - 2.5rem);
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__parallax {
|
.about__parallax {
|
||||||
|
@ -247,10 +374,23 @@ h5 {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history__parallax {
|
||||||
|
max-height: 34rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.history__parallax {
|
||||||
|
max-height: 45rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.about__title {
|
.about__title {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -259,8 +399,10 @@ h5 {
|
||||||
.about__subtitle {
|
.about__subtitle {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
color: #FF3D8C;
|
color: #FF3D8C;
|
||||||
font-size: 1.5rem;
|
font-size: 1.3rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
@ -268,8 +410,29 @@ h5 {
|
||||||
.about__text {
|
.about__text {
|
||||||
font-family: 'Futura', sans-serif;
|
font-family: 'Futura', sans-serif;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
font-size: 1.2rem;
|
font-size: 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.about {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about__title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about__subtitle {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about__text {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|
|
@ -6,16 +6,18 @@
|
||||||
:key="advantage.id"
|
:key="advantage.id"
|
||||||
class="advantage"
|
class="advantage"
|
||||||
>
|
>
|
||||||
<v-row>
|
<v-row align="center" class="pa-2">
|
||||||
<v-col cols="12" md="1">
|
<v-col cols="auto">
|
||||||
<small-icon :icon="advantage.icon" />
|
<small-icon :icon="advantage.icon" />
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-card-title class="advantage__title">
|
<v-card-title class="advantage__title">
|
||||||
{{advantage.title}}
|
{{ advantage.title }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-card-text class="advantage__text">
|
<v-card-text class="advantage__text">
|
||||||
{{ advantage.subtitle }}
|
{{ advantage.subtitle }}
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
|
@ -1,87 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card class="contact">
|
<v-card class="contact">
|
||||||
<v-form v-model="valid">
|
|
||||||
<v-container>
|
|
||||||
<v-card-title class="contact__title">Kontaktujte nás!</v-card-title>
|
<v-card-title class="contact__title">Kontaktujte nás!</v-card-title>
|
||||||
<v-row>
|
<dialog-contact-form />
|
||||||
<v-col
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<v-text-field
|
|
||||||
v-model="fullName"
|
|
||||||
label="Jméno"
|
|
||||||
variant="underlined"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<v-text-field
|
|
||||||
v-model="email"
|
|
||||||
label="Emailová adresa"
|
|
||||||
variant="underlined"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<v-text-field
|
|
||||||
v-model="phone"
|
|
||||||
label="Telefonní číslo"
|
|
||||||
variant="underlined"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-textarea
|
|
||||||
v-model="textField"
|
|
||||||
label="Váš dotaz *"
|
|
||||||
variant="underlined"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-btn
|
|
||||||
class="contact__button"
|
|
||||||
type="submit"
|
|
||||||
@click.prevent="sendContact"
|
|
||||||
>
|
|
||||||
Poslat
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</v-form>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import './assets/css/main.css'
|
|
||||||
import {useAPI} from "~/composables/useAPI";
|
|
||||||
|
|
||||||
const valid = ref<boolean>(false);
|
|
||||||
|
|
||||||
const fullName = ref<string>("");
|
|
||||||
const email = ref<string>("");
|
|
||||||
const phone = ref<string>("");
|
|
||||||
const textField = ref<string>("");
|
|
||||||
|
|
||||||
async function sendContact() {
|
|
||||||
const { data, error } = await useAPI('create-contact/', {
|
|
||||||
method: "POST",
|
|
||||||
body: {
|
|
||||||
name: fullName.value,
|
|
||||||
email: email.value,
|
|
||||||
phone_number: phone.value,
|
|
||||||
content: textField.value,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -2,24 +2,62 @@
|
||||||
<h1 id="courses">Kurzy</h1>
|
<h1 id="courses">Kurzy</h1>
|
||||||
<h2>Vyberte, co Vám nejlépe vyhovuje</h2>
|
<h2>Vyberte, co Vám nejlépe vyhovuje</h2>
|
||||||
|
|
||||||
<v-row class="pricing">
|
<v-row class="pricing">
|
||||||
<v-col
|
<v-col
|
||||||
cols="12"
|
cols="12"
|
||||||
|
sm="6"
|
||||||
md="4"
|
md="4"
|
||||||
|
lg="3"
|
||||||
v-for="(course, index) in courses"
|
v-for="(course, index) in courses"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<v-card class="pricing-box">
|
<v-card class="pricing-box" @click="openDialog = true; chosenCourse = course">
|
||||||
<h3>{{ course.name }}</h3>
|
<div class="pricing-content">
|
||||||
<v-card-subtitle><div class="pricing__subtitle">{{ course.time }}</div></v-card-subtitle>
|
<h3 class="pricing-title">{{ course.name }}</h3>
|
||||||
<v-card-title><div class="pricing__price">{{ course.price }}</div></v-card-title>
|
<v-card-subtitle>
|
||||||
<v-card-text v-if="course.desc"><div class="pricing__subtitle">{{ course.desc }}</div></v-card-text>
|
<div class="pricing__subtitle">{{ course.time }}</div>
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-title>
|
||||||
|
<div class="pricing__price">{{ course.price }}</div>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text v-if="course.desc">
|
||||||
|
<div class="pricing__subtitle pricing-desc">{{ course.desc }}</div>
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
<v-btn class="show_more">
|
<v-btn class="show_more">
|
||||||
Kontaktujte nás
|
Kontaktujte nás
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<template>
|
||||||
|
<v-dialog
|
||||||
|
v-model="openDialog"
|
||||||
|
:fullscreen="$vuetify.display.smAndDown"
|
||||||
|
scrollable
|
||||||
|
transition="dialog-bottom-transition"
|
||||||
|
>
|
||||||
|
<v-card class="contact pa-4">
|
||||||
|
<v-card-title class="contact__dialog__title d-flex align-center">
|
||||||
|
<span class="flex-grow-1 text-center" style="margin-left: 40px;">{{ chosenCourse.name }}</span>
|
||||||
|
<v-btn icon="mdi-close" variant="text" @click="openDialog = false"></v-btn>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text v-if="chosenCourse.desc" class="text-center">
|
||||||
|
<div class="pricing__subtitle text-body-1">{{ chosenCourse.desc }}</div>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
|
|
||||||
|
<v-card-title class="contact__dialog__title text-center">
|
||||||
|
Kontaktujte nás!
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<dialog-contact-form class="mt-2" />
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- <v-btn to="/kurzy" class="show_more">-->
|
<!-- <v-btn to="/kurzy" class="show_more">-->
|
||||||
<!-- <v-icon icon="mdi-chevron-down"/>Více informací<v-icon icon="mdi-chevron-down"/>-->
|
<!-- <v-icon icon="mdi-chevron-down"/>Více informací<v-icon icon="mdi-chevron-down"/>-->
|
||||||
<!-- </v-btn>-->
|
<!-- </v-btn>-->
|
||||||
|
@ -27,7 +65,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import './assets/css/main.css'
|
import './assets/css/main.css'
|
||||||
|
|
||||||
// TODO: vyskakovací okno s kontaktním formulářem po rozkliku :)
|
const openDialog = ref<boolean>(false);
|
||||||
|
|
||||||
const courses = [
|
const courses = [
|
||||||
{name: "Sportovní taneční klub", time: "", price: "", desc: "Připojte se k našemu tanečnímu klubu a rozvíjejte své taneční dovednosti v přátelském prostředí. Nabízíme různé styly tance pro všechny úrovně."},
|
{name: "Sportovní taneční klub", time: "", price: "", desc: "Připojte se k našemu tanečnímu klubu a rozvíjejte své taneční dovednosti v přátelském prostředí. Nabízíme různé styly tance pro všechny úrovně."},
|
||||||
|
@ -38,4 +76,5 @@ const courses = [
|
||||||
{name: "Pronájem sálu", time: "", price: "", desc: "Hledáte ideální prostor pro tanec nebo jinou aktivitu? Náš taneční sál je k dispozici k pronájmu pro Vaše akce."},
|
{name: "Pronájem sálu", time: "", price: "", desc: "Hledáte ideální prostor pro tanec nebo jinou aktivitu? Náš taneční sál je k dispozici k pronájmu pro Vaše akce."},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const chosenCourse = ref(courses[0]);
|
||||||
</script>
|
</script>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<v-parallax
|
<v-parallax
|
||||||
class="about__parallax"
|
class="history__parallax"
|
||||||
src="public/img/black-pink.jpg"
|
src="public/img/black-pink.jpg"
|
||||||
>
|
>
|
||||||
<v-container id="about">
|
<v-container id="about">
|
||||||
|
|
|
@ -17,15 +17,21 @@
|
||||||
aspect-ratio="1"
|
aspect-ratio="1"
|
||||||
cover
|
cover
|
||||||
class="article__image"
|
class="article__image"
|
||||||
|
@click="showCarousel = true"
|
||||||
>
|
>
|
||||||
<template v-slot:placeholder>
|
<template v-slot:placeholder>
|
||||||
<v-row>
|
<v-row justify="center" align="center" class="fill-height">
|
||||||
<v-progress-circular
|
<v-progress-circular color="grey-lighten-5" indeterminate></v-progress-circular>
|
||||||
color="grey-lighten-5"
|
|
||||||
indeterminate
|
|
||||||
></v-progress-circular>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
|
<v-dialog v-model="showCarousel">
|
||||||
|
<dialog-carousel
|
||||||
|
:image="article.image"
|
||||||
|
:images="article.images"
|
||||||
|
:title="article.title"
|
||||||
|
@isActive="showCarousel = false"
|
||||||
|
/>
|
||||||
|
</v-dialog>
|
||||||
</v-img>
|
</v-img>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col
|
<v-col
|
||||||
|
@ -47,13 +53,19 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import './assets/css/main.css'
|
import './assets/css/main.css'
|
||||||
|
|
||||||
import { useAPI } from "~/composables/useAPI";
|
import { useAPI } from "~/composables/useAPI";
|
||||||
|
|
||||||
|
const showCarousel = ref(false);
|
||||||
|
|
||||||
|
interface ArticleImage {
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Article {
|
interface Article {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
image: string;
|
image: string;
|
||||||
|
images: ArticleImage[];
|
||||||
date: string;
|
date: string;
|
||||||
content: string;
|
content: string;
|
||||||
author: string;
|
author: string;
|
||||||
|
@ -70,5 +82,4 @@ else if (error.value) {
|
||||||
console.error("Error loading articles:", error.value);
|
console.error("Error loading articles:", error.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -1,14 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<v-parallax
|
<v-parallax class="trainers__parallax" src="public/img/black-pink.jpg">
|
||||||
class="trainers__parallax"
|
|
||||||
src="public/img/black-pink.jpg"
|
|
||||||
>
|
|
||||||
<v-container id="trainers">
|
<v-container id="trainers">
|
||||||
<v-card class="trainers">
|
<v-card class="trainers pa-4">
|
||||||
<v-card-title class="about__title">Naši trenéři a lektoři</v-card-title>
|
<v-card-title class="about__title ">Naši trenéři a lektoři</v-card-title>
|
||||||
<v-card-subtitle class="about__subtitle">Seznamte se s námi!</v-card-subtitle>
|
<v-card-subtitle class="about__subtitle ">Seznamte se s námi!</v-card-subtitle>
|
||||||
<v-row>
|
|
||||||
<v-col class="text-center" cols="12" md="3" v-for="lector in lectors" :key="lector.id">
|
<v-carousel cycle interval="4000" hide-delimiters show-arrows="hover">
|
||||||
|
<v-carousel-item v-for="(group, index) in trainerGroups" :key="index">
|
||||||
|
<v-row justify="center">
|
||||||
|
<v-col
|
||||||
|
v-for="(lector, index) in group"
|
||||||
|
:key="index"
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="4"
|
||||||
|
lg="3"
|
||||||
|
class="text-center"
|
||||||
|
>
|
||||||
<v-avatar
|
<v-avatar
|
||||||
color="none"
|
color="none"
|
||||||
rounded="1"
|
rounded="1"
|
||||||
|
@ -17,10 +25,12 @@
|
||||||
>
|
>
|
||||||
<v-img :src="lector.img" cover></v-img>
|
<v-img :src="lector.img" cover></v-img>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
<v-card-subtitle class="about__subtitle">{{lector.name}}</v-card-subtitle>
|
<v-card-subtitle class="about__subtitle">{{ lector.name }}</v-card-subtitle>
|
||||||
<v-card-text class="about__text">{{ lector.desc }}</v-card-text>
|
<v-card-text class="about__text">{{ lector.desc }}</v-card-text>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
</v-carousel-item>
|
||||||
|
</v-carousel>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-parallax>
|
</v-parallax>
|
||||||
|
@ -30,28 +40,38 @@ import './assets/css/main.css'
|
||||||
|
|
||||||
const lectors = [
|
const lectors = [
|
||||||
{
|
{
|
||||||
id: 1,
|
|
||||||
name: "Ondřej Gilar",
|
name: "Ondřej Gilar",
|
||||||
img: "/trainers/img.png",
|
img: "/trainers/img.png",
|
||||||
desc: "Trenér - latinskoamerické tance a Pro-AM",
|
desc: "Trenér - latinskoamerické tance a Pro-AM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
|
||||||
name: "Leona Hruštincová",
|
name: "Leona Hruštincová",
|
||||||
img: "/trainers/img.png",
|
img: "/trainers/img.png",
|
||||||
desc: "Lektorka - tance pro děti",
|
desc: "Lektorka - tance pro děti",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
|
||||||
name: "Ondřej Gilar",
|
name: "Ondřej Gilar",
|
||||||
img: "/trainers/img.png",
|
img: "/trainers/img.png",
|
||||||
desc: "Trenér - latinskoamerické tance a Pro-AM",
|
desc: "Trenér - latinskoamerické tance a Pro-AM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
name: "Leona Hruštincová",
|
||||||
|
img: "/trainers/img.png",
|
||||||
|
desc: "Lektorka - tance pro děti",
|
||||||
|
},
|
||||||
|
{
|
||||||
name: "Leona Hruštincová",
|
name: "Leona Hruštincová",
|
||||||
img: "/trainers/img.png",
|
img: "/trainers/img.png",
|
||||||
desc: "Lektorka - tance pro děti",
|
desc: "Lektorka - tance pro děti",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const trainerGroups = computed(() => {
|
||||||
|
const groups: typeof lectors[] = [];
|
||||||
|
const perSlide = window.innerWidth >= 1280 ? 4 : window.innerWidth >= 600 ? 2 : 1;
|
||||||
|
for (let i = 0; i < lectors.length; i += perSlide) {
|
||||||
|
groups.push(lectors.slice(i, i + perSlide));
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
35
services/frontend/components/dialog/Carousel.vue
Normal file
35
services/frontend/components/dialog/Carousel.vue
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<v-card class="article__image">
|
||||||
|
<v-card-title>{{ title }}
|
||||||
|
<v-icon class="to_right" @click="$emit('isActive', false)">mdi-close</v-icon>
|
||||||
|
</v-card-title>
|
||||||
|
<v-carousel
|
||||||
|
:hide-delimiters="true"
|
||||||
|
height="90vh"
|
||||||
|
>
|
||||||
|
<v-carousel-item
|
||||||
|
v-for="(item, i) in images"
|
||||||
|
:key="i"
|
||||||
|
:src="item.image"
|
||||||
|
cover
|
||||||
|
class="carousel__image"
|
||||||
|
></v-carousel-item>
|
||||||
|
</v-carousel>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import './assets/css/main.css';
|
||||||
|
|
||||||
|
interface ArticleImage {
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
image: string;
|
||||||
|
images: ArticleImage[];
|
||||||
|
title: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineEmits(["isActive"])
|
||||||
|
</script>
|
84
services/frontend/components/dialog/ContactForm.vue
Normal file
84
services/frontend/components/dialog/ContactForm.vue
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<template>
|
||||||
|
<v-form v-model="valid">
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<v-text-field
|
||||||
|
v-model="fullName"
|
||||||
|
label="Jméno"
|
||||||
|
variant="underlined"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<v-text-field
|
||||||
|
v-model="email"
|
||||||
|
label="Emailová adresa"
|
||||||
|
variant="underlined"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<v-text-field
|
||||||
|
v-model="phone"
|
||||||
|
label="Telefonní číslo"
|
||||||
|
variant="underlined"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-textarea
|
||||||
|
v-model="textField"
|
||||||
|
label="Váš dotaz"
|
||||||
|
variant="underlined"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-btn
|
||||||
|
class="contact__button"
|
||||||
|
type="submit"
|
||||||
|
@click.prevent="sendContact"
|
||||||
|
>
|
||||||
|
Poslat
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-form>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import './assets/css/main.css'
|
||||||
|
import {useAPI} from "~/composables/useAPI";
|
||||||
|
|
||||||
|
const valid = ref<boolean>(false);
|
||||||
|
|
||||||
|
const fullName = ref<string>("");
|
||||||
|
const email = ref<string>("");
|
||||||
|
const phone = ref<string>("");
|
||||||
|
const textField = ref<string>("");
|
||||||
|
|
||||||
|
async function sendContact() {
|
||||||
|
const { data, error } = await useAPI('create-contact/', {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
name: fullName.value,
|
||||||
|
email: email.value,
|
||||||
|
phone_number: phone.value,
|
||||||
|
content: textField.value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -11,15 +11,31 @@
|
||||||
</div>
|
</div>
|
||||||
<v-app-bar-title class="app__title">Taneční klub Ostrava</v-app-bar-title>
|
<v-app-bar-title class="app__title">Taneční klub Ostrava</v-app-bar-title>
|
||||||
|
|
||||||
<v-tabs
|
<v-tabs v-model="currentTab" class="d-none d-sm-flex app__tab">
|
||||||
v-for="(tab, index) in tabs" :key="index"
|
<div
|
||||||
v-model="currentTab"
|
v-for="(tab, index) in tabs"
|
||||||
align-with-title
|
:key="index"
|
||||||
class="d-none d-sm-flex app__tab"
|
|
||||||
>
|
>
|
||||||
<v-tab v-if="tab.ref" :text="tab.name" :value="tab.name" @click="useGoTo(tab.ref)"></v-tab>
|
<v-tab
|
||||||
<v-tab v-if="tab.href" :text="tab.name" :value="tab.name" :href="tab.href"></v-tab>
|
v-if="tab.ref"
|
||||||
|
:text="tab.name"
|
||||||
|
:value="tab.name"
|
||||||
|
@click="useGoTo(tab.ref)"
|
||||||
|
>
|
||||||
|
{{ tab.name }}
|
||||||
|
</v-tab>
|
||||||
|
<v-tab
|
||||||
|
v-if="tab.href"
|
||||||
|
:text="tab.name"
|
||||||
|
:value="tab.name"
|
||||||
|
:href="tab.href"
|
||||||
|
>
|
||||||
|
{{ tab.name }}
|
||||||
|
</v-tab>
|
||||||
|
</div>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
|
|
||||||
|
|
||||||
<!-- <v-col-->
|
<!-- <v-col-->
|
||||||
<!-- class="text-right"-->
|
<!-- class="text-right"-->
|
||||||
<!-- @click="toggleTheme"-->
|
<!-- @click="toggleTheme"-->
|
||||||
|
@ -29,7 +45,6 @@
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
|
|
||||||
<v-fab
|
<v-fab
|
||||||
:key="currentTab.ref"
|
|
||||||
class="ms-4 mb-4"
|
class="ms-4 mb-4"
|
||||||
></v-fab>
|
></v-fab>
|
||||||
|
|
||||||
|
@ -39,10 +54,7 @@
|
||||||
fixed
|
fixed
|
||||||
left
|
left
|
||||||
>
|
>
|
||||||
<v-list
|
<v-list nav dense>
|
||||||
nav
|
|
||||||
dense
|
|
||||||
>
|
|
||||||
<v-list-item v-for="(tab, index) in tabs" :key="index">
|
<v-list-item v-for="(tab, index) in tabs" :key="index">
|
||||||
<v-list-item-title @click="useGoTo(tab.ref)">{{ tab.name }}</v-list-item-title>
|
<v-list-item-title @click="useGoTo(tab.ref)">{{ tab.name }}</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
@ -54,18 +66,31 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGoTo } from "~/composables/useGoTo";
|
import { useGoTo } from "~/composables/useGoTo";
|
||||||
|
import {useRoute} from "#vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const currentTab = ref({name: 'O nás', ref: "#about", href: "o-nas"});
|
const currentTab = ref({ name: 'O nás', ref: "#about", href: "o-nas" });
|
||||||
const drawer = ref(null)
|
const drawer = ref(null)
|
||||||
const tabs = [
|
|
||||||
{name: 'O nás', ref: "#about", href: ""},
|
|
||||||
{name: "Trenéři", ref: "#trainers", href: ""},
|
|
||||||
{name: 'Kurzy', ref: "#courses", href: ""},
|
|
||||||
{name: 'Galerie', ref: "", href: "/galerie"},
|
|
||||||
{name: 'Aktuality', ref: "#article", href: ""},
|
|
||||||
{name: 'Kontakty', ref: "", href: "/kontakty"},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
const homeTabs = [
|
||||||
|
{ name: "O nás", ref: "#about", href: "" },
|
||||||
|
{ name: "Trenéři", ref: "#trainers", href: "" },
|
||||||
|
{ name: "Kurzy", ref: "#courses", href: "" },
|
||||||
|
{ name: "Galerie", ref: "", href: "/galerie" },
|
||||||
|
{ name: "Aktuality", ref: "#article", href: "" },
|
||||||
|
{ name: "Kontakty", ref: "", href: "/kontakty" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const otherTabs = [
|
||||||
|
{ name: "O nás", ref: "", href: "/" },
|
||||||
|
{ name: "Trenéři", ref: "", href: "/" },
|
||||||
|
{ name: "Kurzy", ref: "", href: "/" },
|
||||||
|
{ name: "Galerie", ref: "", href: "/galerie" },
|
||||||
|
{ name: "Aktuality", ref: "", href: "/" },
|
||||||
|
{ name: "Kontakty", ref: "", href: "/kontakty" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const tabs = computed(() => (route.path === "/" ? homeTabs : otherTabs));
|
||||||
// import { useTheme } from 'vuetify'
|
// import { useTheme } from 'vuetify'
|
||||||
//
|
//
|
||||||
// const theme = useTheme()
|
// const theme = useTheme()
|
||||||
|
@ -74,4 +99,5 @@ const tabs = [
|
||||||
// theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
|
// theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -1,40 +1,66 @@
|
||||||
function waitForElement (selector: string, timeout = 2000) : Promise<Element> {
|
import { useRouter, useRoute } from "vue-router";
|
||||||
|
import { nextTick } from "vue";
|
||||||
|
|
||||||
|
export async function useGoTo(
|
||||||
|
selector: string,
|
||||||
|
props: { offset?: number } = {}
|
||||||
|
): Promise<void> {
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const yOffset = props?.offset ?? -80;
|
||||||
|
|
||||||
|
const hash = selector.startsWith("#") ? selector : "";
|
||||||
|
const path = selector.startsWith("/") ? selector : route.path + hash;
|
||||||
|
|
||||||
|
// If navigating to another page
|
||||||
|
if (route.path !== path.split("#")[0]) {
|
||||||
|
await router.push(path);
|
||||||
|
await nextTick(); // Ensure DOM updates
|
||||||
|
|
||||||
|
// Wait for the element to exist before scrolling
|
||||||
|
try {
|
||||||
|
const element = await waitForElement(hash);
|
||||||
|
scrollToElement(element, yOffset);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If already on the correct page, scroll immediately
|
||||||
|
try {
|
||||||
|
const element = await waitForElement(hash);
|
||||||
|
scrollToElement(element, yOffset);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to the element smoothly
|
||||||
|
function scrollToElement(element: Element, offset: number) {
|
||||||
|
const y = element.getBoundingClientRect().top + window.pageYOffset + offset;
|
||||||
|
window.scrollTo({ top: y, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the element to exist
|
||||||
|
function waitForElement(selector: string, timeout = 2000): Promise<Element> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
// eslint-disable-next-line prefer-const
|
|
||||||
let observer: MutationObserver;
|
let observer: MutationObserver;
|
||||||
|
|
||||||
function checkElement () {
|
function checkElement() {
|
||||||
const element = document.querySelector(selector);
|
const element = document.querySelector(selector);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
|
observer.disconnect(); // Stop observing DOM changes
|
||||||
resolve(element);
|
resolve(element);
|
||||||
observer.disconnect(); // Stop observing DOM changes
|
|
||||||
} else if (Date.now() - startTime >= timeout) {
|
} else if (Date.now() - startTime >= timeout) {
|
||||||
reject(new Error(`Timeout exceeded while waiting for element with selector '${selector}'`));
|
|
||||||
observer.disconnect(); // Stop observing DOM changes
|
observer.disconnect(); // Stop observing DOM changes
|
||||||
|
reject(new Error(`Timeout exceeded while waiting for element '${selector}'`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
observer = new MutationObserver(checkElement);
|
observer = new MutationObserver(checkElement);
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
checkElement(); // Check initially in case the element is already present
|
checkElement(); // Initial check in case the element already exists
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function useGoTo (selector: string, props: {offset? :number} = {}): Promise<void> {
|
|
||||||
console.log(selector);
|
|
||||||
let element: Element;
|
|
||||||
try {
|
|
||||||
element = await waitForElement(selector);
|
|
||||||
} catch {
|
|
||||||
// element not found
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const yOffset = props?.offset ?? -80;
|
|
||||||
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
|
||||||
|
|
||||||
window.scrollTo({ top: y, behavior: "smooth" });
|
|
||||||
}
|
|
|
@ -2,16 +2,17 @@
|
||||||
<h1>Galerie</h1>
|
<h1>Galerie</h1>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col
|
<v-col
|
||||||
v-for="(photo, index) in photos"
|
v-for="(photo, index) in gallery"
|
||||||
:key="index"
|
:key="index"
|
||||||
cols="4"
|
cols="4"
|
||||||
style="padding: 0;"
|
style="padding: 0;"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img
|
||||||
:lazy-src="photo.src"
|
:lazy-src="photo.image"
|
||||||
:src="photo.src"
|
:src="photo.image"
|
||||||
aspect-ratio="1"
|
aspect-ratio="1"
|
||||||
cover
|
cover
|
||||||
|
@click="showCarousel = true"
|
||||||
>
|
>
|
||||||
<template v-slot:placeholder>
|
<template v-slot:placeholder>
|
||||||
<v-row>
|
<v-row>
|
||||||
|
@ -21,43 +22,37 @@
|
||||||
></v-progress-circular>
|
></v-progress-circular>
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
|
<v-dialog v-model="showCarousel">
|
||||||
|
<dialog-carousel
|
||||||
|
:image="photo.image"
|
||||||
|
:images="gallery"
|
||||||
|
:title="photo.title"
|
||||||
|
@isActive="showCarousel = false"
|
||||||
|
/>
|
||||||
|
</v-dialog>
|
||||||
</v-img>
|
</v-img>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import './assets/css/main.css'
|
import './assets/css/main.css'
|
||||||
|
import { useAPI } from "~/composables/useAPI";
|
||||||
|
|
||||||
const photos = [
|
const showCarousel = ref(false);
|
||||||
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
|
|
||||||
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
|
interface ArticleImage {
|
||||||
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
|
image: string;
|
||||||
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
|
title: string;
|
||||||
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
|
}
|
||||||
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
|
|
||||||
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
|
const gallery = ref<ArticleImage[]>([]);
|
||||||
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
|
|
||||||
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
|
const { error, data } = await useAPI<ArticleImage[]>('load-gallery/', {method: "GET"});
|
||||||
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
|
|
||||||
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
|
if ( data.value ){
|
||||||
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
|
gallery.value = data.value as ArticleImage[];
|
||||||
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
|
}
|
||||||
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
|
else if (error.value) {
|
||||||
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
|
console.error("Error loading gallery:", error.value);
|
||||||
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
|
}
|
||||||
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
|
|
||||||
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
|
|
||||||
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
|
|
||||||
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
|
|
||||||
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
|
|
||||||
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
|
|
||||||
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
|
|
||||||
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
|
|
||||||
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
|
|
||||||
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
|
|
||||||
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
|
|
||||||
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
|
|
||||||
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
|
|
||||||
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
|
@ -49,7 +49,7 @@ export default defineNuxtPlugin((app) => {
|
||||||
defaults: {
|
defaults: {
|
||||||
SmallIcon: {
|
SmallIcon: {
|
||||||
color: 'color',
|
color: 'color',
|
||||||
style: [{ 'margin-left': '20px', 'margin-top': '15px', 'font-size': '30px'}]
|
style: [{ 'margin-left': '20px', 'font-size': '30px'}]
|
||||||
},
|
},
|
||||||
BigIcon: {
|
BigIcon: {
|
||||||
color: 'color',
|
color: 'color',
|
||||||
|
|
Loading…
Reference in a new issue