From 564418501c1f104c6249147c6ddbdba15e0d0f99 Mon Sep 17 00:00:00 2001 From: Brunobrno Date: Fri, 12 Dec 2025 01:52:41 +0100 Subject: [PATCH] Refactor email system and add contact form backend Refactored email sending to use a single HTML template with a base layout, removed plain text email templates, and updated all related backend logic. Introduced a new ContactMe model, serializer, Celery task, and API endpoints for handling contact form submissions, including email notifications. Renamed ShopConfiguration to SiteConfiguration throughout the backend for consistency. Updated frontend to remove unused components, add a new Services section, and adjust navigation and contact form integration. --- backend/account/tasks.py | 70 +++-------- .../templates/email/email_verification.html | 65 +++------- .../templates/email/email_verification.txt | 7 -- .../templates/email/password_reset.html | 65 +++------- .../templates/email/password_reset.txt | 7 -- backend/account/templates/email/test.html | 61 +++------- backend/account/templates/email/test.txt | 6 - backend/advertisement/models.py | 11 ++ backend/advertisement/serializer.py | 9 ++ backend/advertisement/tasks.py | 19 ++- .../templates/email/contact_me.html | 6 + backend/advertisement/urls.py | 15 +++ backend/advertisement/views.py | 47 ++++++- backend/commerce/models.py | 12 +- backend/configuration/apps.py | 7 +- backend/configuration/models.py | 2 +- backend/configuration/serializers.py | 10 +- backend/configuration/urls.py | 7 +- backend/configuration/views.py | 18 +-- .../email/{ => components}/base.html | 0 backend/thirdparty/zasilkovna/models.py | 6 +- backend/thirdparty/zasilkovna/views.py | 4 +- backend/vontor_cz/urls.py | 2 +- frontend/src/App.tsx | 2 - .../Forms/ContactMe/ContactMeForm.tsx | 1 + frontend/src/components/hero/HeroCarousel.tsx | 8 -- .../hosting/HostingSecuritySection.tsx | 21 ---- frontend/src/components/navbar/SiteNav.tsx | 7 +- .../src/components/navbar/navbar.module.css | 7 +- frontend/src/components/services/Services.tsx | 115 ++++++++++++++++++ .../components/services/services.module.css | 93 ++++++++++++++ .../src/components/skills/SkillsSection.tsx | 35 ------ frontend/src/pages/home/home.tsx | 11 +- .../src/pages/hosting/HostingSecurityPage.tsx | 4 - 34 files changed, 433 insertions(+), 327 deletions(-) delete mode 100644 backend/account/templates/email/email_verification.txt delete mode 100644 backend/account/templates/email/password_reset.txt delete mode 100644 backend/account/templates/email/test.txt create mode 100644 backend/advertisement/serializer.py create mode 100644 backend/advertisement/templates/email/contact_me.html create mode 100644 backend/advertisement/urls.py rename backend/templates/email/{ => components}/base.html (100%) delete mode 100644 frontend/src/components/hero/HeroCarousel.tsx delete mode 100644 frontend/src/components/hosting/HostingSecuritySection.tsx create mode 100644 frontend/src/components/services/Services.tsx create mode 100644 frontend/src/components/services/services.module.css delete mode 100644 frontend/src/components/skills/SkillsSection.tsx delete mode 100644 frontend/src/pages/hosting/HostingSecurityPage.tsx diff --git a/backend/account/tasks.py b/backend/account/tasks.py index 565d65a..c8427ad 100644 --- a/backend/account/tasks.py +++ b/backend/account/tasks.py @@ -10,34 +10,25 @@ from .models import CustomUser logger = get_task_logger(__name__) -# TODO: předělat funkci ať funguje s base.html šablonou na emaily !!! -def send_email_with_context(recipients, subject, message=None, template_name=None, html_template_name=None, context=None): +def send_email_with_context(recipients, subject, template_path=None, context=None, message: str | None = None): """ - General function to send emails with a specific context. - Supports rendering plain text and HTML templates. - Converts `user` in context to a plain dict to avoid template access to the model. + Send emails rendering a single HTML template. + - `template_name` is a simple base name without extension, e.g. "email/test". + - Renders only HTML (".html"), no ".txt" support. + - Converts `user` in context to a plain dict to avoid passing models to templates. """ if isinstance(recipients, str): recipients = [recipients] html_message = None - if template_name or html_template_name: - # Best effort to resolve both templates if only one provided - if not template_name and html_template_name: - template_name = html_template_name.replace(".html", ".txt") - if not html_template_name and template_name: - html_template_name = template_name.replace(".txt", ".html") - + if template_path: ctx = dict(context or {}) - # Sanitize user if someone passes the model by mistake - if "user" in ctx and not isinstance(ctx["user"], dict): - try: - ctx["user"] = _build_user_template_ctx(ctx["user"]) - except Exception: - ctx["user"] = {} - - message = render_to_string(template_name, ctx) - html_message = render_to_string(html_template_name, ctx) + # Render base layout and include the provided template as the main content. + # The included template receives the same context as the base. + html_message = render_to_string( + "email/components/base.html", + {"content_template": template_path, **ctx}, + ) try: send_mail( @@ -48,33 +39,13 @@ def send_email_with_context(recipients, subject, message=None, template_name=Non fail_silently=False, html_message=html_message, ) - if settings.EMAIL_BACKEND == 'django.core.mail.backends.console.EmailBackend': + if settings.EMAIL_BACKEND == 'django.core.mail.backends.console.EmailBackend' and message: logger.debug(f"\nEMAIL OBSAH:\n{message}\nKONEC OBSAHU") return True except Exception as e: logger.error(f"E-mail se neodeslal: {e}") return False - - -def _build_user_template_ctx(user: CustomUser) -> dict: - """ - Return a plain dict for templates instead of passing the DB model. - Provides aliases to avoid template errors (firstname vs first_name). - Adds a backward-compatible key 'get_full_name' for templates using `user.get_full_name`. - """ - first_name = getattr(user, "first_name", "") or "" - last_name = getattr(user, "last_name", "") or "" - full_name = f"{first_name} {last_name}".strip() - return { - "id": user.pk, - "email": getattr(user, "email", "") or "", - "first_name": first_name, - "firstname": first_name, # alias for templates using `firstname` - "last_name": last_name, - "lastname": last_name, # alias for templates using `lastname` - "full_name": full_name, - "get_full_name": full_name, # compatibility for templates using method-style access - } + #---------------------------------------------------------------------------------------------------- @@ -93,7 +64,7 @@ def send_email_verification_task(user_id): verify_url = f"{settings.FRONTEND_URL}/email-verification/?uidb64={uid}&token={token}" context = { - "user": _build_user_template_ctx(user), + "user": user, "action_url": verify_url, "frontend_url": settings.FRONTEND_URL, "cta_label": "Ověřit e‑mail", @@ -102,8 +73,7 @@ def send_email_verification_task(user_id): send_email_with_context( recipients=user.email, subject="Ověření e‑mailu", - template_name="email/email_verification.txt", - html_template_name="email/email_verification.html", + template_path="email/email_verification.html", context=context, ) @@ -119,8 +89,7 @@ def send_email_test_task(email): send_email_with_context( recipients=email, subject="Testovací e‑mail", - template_name="email/test.txt", - html_template_name="email/test.html", + template_path="email/test.html", context=context, ) @@ -138,7 +107,7 @@ def send_password_reset_email_task(user_id): reset_url = f"{settings.FRONTEND_URL}/reset-password/{uid}/{token}" context = { - "user": _build_user_template_ctx(user), + "user": user, "action_url": reset_url, "frontend_url": settings.FRONTEND_URL, "cta_label": "Obnovit heslo", @@ -147,7 +116,6 @@ def send_password_reset_email_task(user_id): send_email_with_context( recipients=user.email, subject="Obnova hesla", - template_name="email/password_reset.txt", - html_template_name="email/password_reset.html", + template_path="email/password_reset.html", context=context, ) \ No newline at end of file diff --git a/backend/account/templates/email/email_verification.html b/backend/account/templates/email/email_verification.html index 0802339..8381034 100644 --- a/backend/account/templates/email/email_verification.html +++ b/backend/account/templates/email/email_verification.html @@ -1,46 +1,21 @@ - - - - - - - -
- - - - - - - -
- Ověření e‑mailu -
- {% with name=user.first_name|default:user.firstname|default:user.get_full_name %} -

Dobrý den{% if name %} {{ name }}{% endif %},

- {% endwith %} -

Děkujeme za registraci. Prosíme, ověřte svou e‑mailovou adresu kliknutím na tlačítko níže.

+

Ověření e‑mailu

- {% if action_url and cta_label %} - - - - -
- - {{ cta_label }} - -
-

Pokud tlačítko nefunguje, zkopírujte do prohlížeče tento odkaz:
{{ action_url }}

- {% endif %} -
- - - - -
- Tento e‑mail byl odeslán z aplikace e‑tržnice. -
-
- - +
+ {% with name=user.first_name|default:user.firstname|default:user.get_full_name %} +

Dobrý den{% if name %} {{ name }}{% endif %},

+ {% endwith %} +

Děkujeme za registraci. Prosíme, ověřte svou e‑mailovou adresu kliknutím na tlačítko níže.

+ + {% if action_url and cta_label %} + + + + +
+ + {{ cta_label }} + +
+

Pokud tlačítko nefunguje, zkopírujte do prohlížeče tento odkaz:
{{ action_url }}

+ {% endif %} +
diff --git a/backend/account/templates/email/email_verification.txt b/backend/account/templates/email/email_verification.txt deleted file mode 100644 index 042e029..0000000 --- a/backend/account/templates/email/email_verification.txt +++ /dev/null @@ -1,7 +0,0 @@ -{% with name=user.first_name|default:user.firstname|default:user.get_full_name %}Dobrý den{% if name %} {{ name }}{% endif %},{% endwith %} - -Děkujeme za registraci. Prosíme, ověřte svou e‑mailovou adresu kliknutím na následující odkaz: - -{{ action_url }} - -Pokud jste účet nevytvořili vy, tento e‑mail ignorujte. diff --git a/backend/account/templates/email/password_reset.html b/backend/account/templates/email/password_reset.html index 58d403f..30baeaf 100644 --- a/backend/account/templates/email/password_reset.html +++ b/backend/account/templates/email/password_reset.html @@ -1,46 +1,21 @@ - - - - - - - -
- - - - - - - -
- Obnova hesla -
- {% with name=user.first_name|default:user.firstname|default:user.get_full_name %} -

Dobrý den{% if name %} {{ name }}{% endif %},

- {% endwith %} -

Obdrželi jste tento e‑mail, protože byla požádána obnova hesla k vašemu účtu. Pokud jste o změnu nepožádali, tento e‑mail ignorujte.

+

Obnova hesla

- {% if action_url and cta_label %} - - - - -
- - {{ cta_label }} - -
-

Pokud tlačítko nefunguje, zkopírujte do prohlížeče tento odkaz:
{{ action_url }}

- {% endif %} -
- - - - -
- Tento e‑mail byl odeslán z aplikace e‑tržnice. -
-
- - +
+ {% with name=user.first_name|default:user.firstname|default:user.get_full_name %} +

Dobrý den{% if name %} {{ name }}{% endif %},

+ {% endwith %} +

Obdrželi jste tento e‑mail, protože byla požádána obnova hesla k vašemu účtu. Pokud jste o změnu nepožádali, tento e‑mail ignorujte.

+ + {% if action_url and cta_label %} + + + + +
+ + {{ cta_label }} + +
+

Pokud tlačítko nefunguje, zkopírujte do prohlížeče tento odkaz:
{{ action_url }}

+ {% endif %} +
diff --git a/backend/account/templates/email/password_reset.txt b/backend/account/templates/email/password_reset.txt deleted file mode 100644 index 27638d5..0000000 --- a/backend/account/templates/email/password_reset.txt +++ /dev/null @@ -1,7 +0,0 @@ -{% with name=user.first_name|default:user.firstname|default:user.get_full_name %}Dobrý den{% if name %} {{ name }}{% endif %},{% endwith %} - -Obdrželi jste tento e‑mail, protože byla požádána obnova hesla k vašemu účtu. -Pokud jste o změnu nepožádali, tento e‑mail ignorujte. - -Pro nastavení nového hesla použijte tento odkaz: -{{ action_url }} diff --git a/backend/account/templates/email/test.html b/backend/account/templates/email/test.html index 6721080..55685d7 100644 --- a/backend/account/templates/email/test.html +++ b/backend/account/templates/email/test.html @@ -1,44 +1,19 @@ - - - - - - - -
- - - - - - - -
- Testovací e‑mail -
-

Dobrý den,

-

Toto je testovací e‑mail z aplikace e‑tržnice.

+

Testovací e‑mail

- {% if action_url and cta_label %} - - - - -
- - {{ cta_label }} - -
-

Pokud tlačítko nefunguje, zkopírujte do prohlížeče tento odkaz:
{{ action_url }}

- {% endif %} -
- - - - -
- Tento e‑mail byl odeslán z aplikace e‑tržnice. -
-
- - +
+

Dobrý den,

+

Toto je testovací e‑mail z aplikace e‑tržnice.

+ + {% if action_url and cta_label %} + + + + +
+ + {{ cta_label }} + +
+

Pokud tlačítko nefunguje, zkopírujte do prohlížeče tento odkaz:
{{ action_url }}

+ {% endif %} +
diff --git a/backend/account/templates/email/test.txt b/backend/account/templates/email/test.txt deleted file mode 100644 index 79ecc54..0000000 --- a/backend/account/templates/email/test.txt +++ /dev/null @@ -1,6 +0,0 @@ -Dobrý den, - -Toto je testovací e‑mail z aplikace e‑tržnice. - -Odkaz na aplikaci: -{{ action_url }} diff --git a/backend/advertisement/models.py b/backend/advertisement/models.py index 71a8362..fbe91db 100644 --- a/backend/advertisement/models.py +++ b/backend/advertisement/models.py @@ -1,3 +1,14 @@ from django.db import models # Create your models here. + +class ContactMe(models.Model): + client_email = models.EmailField() + content = models.TextField() + + sent_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Email to {self.client_email} sent at {self.sent_at}" + + \ No newline at end of file diff --git a/backend/advertisement/serializer.py b/backend/advertisement/serializer.py new file mode 100644 index 0000000..87fc8a0 --- /dev/null +++ b/backend/advertisement/serializer.py @@ -0,0 +1,9 @@ +from rest_framework import serializers +from .models import ContactMe + + +class ContactMeSerializer(serializers.ModelSerializer): + class Meta: + model = ContactMe + fields = ["id", "client_email", "content", "sent_at"] + read_only_fields = ["id", "sent_at"] diff --git a/backend/advertisement/tasks.py b/backend/advertisement/tasks.py index a5bec03..e9d97c5 100644 --- a/backend/advertisement/tasks.py +++ b/backend/advertisement/tasks.py @@ -1,2 +1,17 @@ -#udělat zasílaní reklamních emailů uživatelům. -#newletter --> když se vytvoří nový record s reklamou email se uloží pomocí zaškrtnutí tlačítka v záznamu \ No newline at end of file +from account.tasks import send_email_with_context +from configuration.models import SiteConfiguration + +from celery import shared_task + +@shared_task +def send_contact_me_email_task(client_email, message_content): + context = { + "client_email": client_email, + "message_content": message_content + } + send_email_with_context( + recipients=SiteConfiguration.get_solo().contact_email, + subject="Poptávka z kontaktního formuláře!!!", + template_path="email/contact_me.html", + context=context, + ) \ No newline at end of file diff --git a/backend/advertisement/templates/email/contact_me.html b/backend/advertisement/templates/email/contact_me.html new file mode 100644 index 0000000..2c0adb4 --- /dev/null +++ b/backend/advertisement/templates/email/contact_me.html @@ -0,0 +1,6 @@ +

Nová zpráva z kontaktního formuláře

+
+

Email odesílatele: {{ client_email }}

+

Zpráva:

+
{{ message_content }}
+
diff --git a/backend/advertisement/urls.py b/backend/advertisement/urls.py new file mode 100644 index 0000000..ad96ccc --- /dev/null +++ b/backend/advertisement/urls.py @@ -0,0 +1,15 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter + +from .views import ContactMePublicView, ContactMeAdminViewSet + +router = DefaultRouter() +router.register(r"contact-messages", ContactMeAdminViewSet, basename="contactme") + +urlpatterns = [ + # Public endpoint + path("contact-me/", ContactMePublicView.as_view(), name="contact-me"), + + # Admin endpoints + path("", include(router.urls)), +] diff --git a/backend/advertisement/views.py b/backend/advertisement/views.py index 91ea44a..bb2e42a 100644 --- a/backend/advertisement/views.py +++ b/backend/advertisement/views.py @@ -1,3 +1,46 @@ -from django.shortcuts import render +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status, viewsets +from rest_framework.permissions import AllowAny, IsAdminUser +from rest_framework.authentication import SessionAuthentication + +from .models import ContactMe +from .serializer import ContactMeSerializer +from .tasks import send_contact_me_email_task + + +class ContactMePublicView(APIView): + permission_classes = [AllowAny] + # Avoid CSRF for public endpoint by disabling SessionAuthentication + authentication_classes = [] + + def post(self, request): + email = request.data.get("email") + message = request.data.get("message") + honeypot = request.data.get("hp") # hidden honeypot field + + # If honeypot is filled, pretend success without processing + if honeypot: + return Response({"status": "ok"}, status=status.HTTP_200_OK) + + if not email or not message: + return Response({"detail": "Missing email or message."}, status=status.HTTP_400_BAD_REQUEST) + + # Save to DB + cm = ContactMe.objects.create(client_email=email, content=message) + + # Send email via Celery task + try: + send_contact_me_email_task.delay(email, message) + except Exception: + # Fallback to direct call if Celery is not running in DEV + send_contact_me_email_task(email, message) + + return Response({"id": cm.id, "status": "queued"}, status=status.HTTP_201_CREATED) + + +class ContactMeAdminViewSet(viewsets.ModelViewSet): + queryset = ContactMe.objects.all().order_by("-sent_at") + serializer_class = ContactMeSerializer + permission_classes = [IsAdminUser] -# Create your views here. diff --git a/backend/commerce/models.py b/backend/commerce/models.py index ea41cef..7eae1ae 100644 --- a/backend/commerce/models.py +++ b/backend/commerce/models.py @@ -10,7 +10,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from weasyprint import HTML import os -from configuration.models import ShopConfiguration +from configuration.models import SiteConfiguration from thirdparty.zasilkovna.models import ZasilkovnaPacket from thirdparty.stripe.models import StripeModel @@ -240,7 +240,7 @@ class Carrier(models.Model): def get_price(self): if self.shipping_method == self.SHIPPING.ZASILKOVNA: - return ShopConfiguration.get_solo().zasilkovna_shipping_price + return SiteConfiguration.get_solo().zasilkovna_shipping_price else: return Decimal('0.0') @@ -285,10 +285,10 @@ class Carrier(models.Model): class Payment(models.Model): class PAYMENT(models.TextChoices): - SHOP = "shop", "cz#Platba v obchodě" + Site = "Site", "cz#Platba v obchodě" STRIPE = "stripe", "cz#Bankovní převod" CASH_ON_DELIVERY = "cash_on_delivery", "cz#Dobírka" - payment_method = models.CharField(max_length=30, choices=PAYMENT.choices, default=PAYMENT.SHOP) + payment_method = models.CharField(max_length=30, choices=PAYMENT.choices, default=PAYMENT.Site) #FIXME: potvrdit že logika platby funguje správně #veškera logika a interakce bude na stripu (třeba aktualizovaní objednávky na zaplacenou apod.) @@ -360,7 +360,7 @@ class OrderItem(models.Model): def get_total_price(self, discounts: list[DiscountCode] = None): """Vrátí celkovou cenu položky po aplikaci relevantních kupónů. - Logika dle ShopConfiguration: + Logika dle SiteConfiguration: - multiplying_coupons=True: procentuální slevy se násobí (sekvenčně) P * (1 - p1) -> výsledné * (1 - p2) ... jinak se použije pouze nejlepší (nejvyšší procento). @@ -375,7 +375,7 @@ class OrderItem(models.Model): return base_price - config = ShopConfiguration.get_solo() + config = SiteConfiguration.get_solo() #seznám slev applicable_percent_discounts: list[int] = [] diff --git a/backend/configuration/apps.py b/backend/configuration/apps.py index 8cc1282..a938a13 100644 --- a/backend/configuration/apps.py +++ b/backend/configuration/apps.py @@ -6,14 +6,13 @@ class ConfigurationConfig(AppConfig): name = 'configuration' def ready(self): - """Ensure the ShopConfiguration singleton exists at startup. - + """Ensure the SiteConfiguration singleton exists at startup. Wrapped in broad DB error handling so that commands like makemigrations/migrate don't fail when the table does not yet exist. """ try: - from .models import ShopConfiguration # local import to avoid premature app registry access - ShopConfiguration.get_solo() # creates if missing + from .models import SiteConfiguration # local import to avoid premature app registry access + SiteConfiguration.get_solo() # creates if missing except (OperationalError, ProgrammingError): # DB not ready (e.g., before initial migrate); ignore silently diff --git a/backend/configuration/models.py b/backend/configuration/models.py index c69de8f..be1085b 100644 --- a/backend/configuration/models.py +++ b/backend/configuration/models.py @@ -2,7 +2,7 @@ from django.db import models # Create your models here. -class ShopConfiguration(models.Model): +class SiteConfiguration(models.Model): name = models.CharField(max_length=100, default="Shop name", unique=True) logo = models.ImageField(upload_to='shop_logos/', blank=True, null=True) diff --git a/backend/configuration/serializers.py b/backend/configuration/serializers.py index 9979183..4b7af80 100644 --- a/backend/configuration/serializers.py +++ b/backend/configuration/serializers.py @@ -1,10 +1,10 @@ from rest_framework import serializers -from .models import ShopConfiguration +from .models import SiteConfiguration -class ShopConfigurationAdminSerializer(serializers.ModelSerializer): +class SiteConfigurationAdminSerializer(serializers.ModelSerializer): class Meta: - model = ShopConfiguration + model = SiteConfiguration fields = [ "id", "name", @@ -29,9 +29,9 @@ class ShopConfigurationAdminSerializer(serializers.ModelSerializer): ] -class ShopConfigurationPublicSerializer(serializers.ModelSerializer): +class SiteConfigurationPublicSerializer(serializers.ModelSerializer): class Meta: - model = ShopConfiguration + model = SiteConfiguration # Expose only non-sensitive fields fields = [ "id", diff --git a/backend/configuration/urls.py b/backend/configuration/urls.py index c84fcaf..b3cdfd9 100644 --- a/backend/configuration/urls.py +++ b/backend/configuration/urls.py @@ -1,8 +1,7 @@ from rest_framework.routers import DefaultRouter -from .views import ShopConfigurationAdminViewSet, ShopConfigurationPublicViewSet +from .views import SiteConfigurationAdminViewSet, SiteConfigurationPublicViewSet router = DefaultRouter() -router.register(r"admin/shop-configuration", ShopConfigurationAdminViewSet, basename="shop-config-admin") -router.register(r"public/shop-configuration", ShopConfigurationPublicViewSet, basename="shop-config-public") - +router.register(r"admin/shop-configuration", SiteConfigurationAdminViewSet, basename="shop-config-admin") +router.register(r"public/shop-configuration", SiteConfigurationPublicViewSet, basename="shop-config-public") urlpatterns = router.urls diff --git a/backend/configuration/views.py b/backend/configuration/views.py index 33be7e9..6d92d6e 100644 --- a/backend/configuration/views.py +++ b/backend/configuration/views.py @@ -1,25 +1,25 @@ from rest_framework import viewsets, mixins from rest_framework.permissions import IsAdminUser, AllowAny -from .models import ShopConfiguration +from .models import SiteConfiguration from .serializers import ( - ShopConfigurationAdminSerializer, - ShopConfigurationPublicSerializer, + SiteConfigurationAdminSerializer, + SiteConfigurationPublicSerializer, ) class _SingletonQuerysetMixin: def get_queryset(self): - return ShopConfiguration.objects.filter(pk=1) + return SiteConfiguration.objects.filter(pk=1) def get_object(self): - return ShopConfiguration.get_solo() + return SiteConfiguration.get_solo() -class ShopConfigurationAdminViewSet(_SingletonQuerysetMixin, viewsets.ModelViewSet): +class SiteConfigurationAdminViewSet(_SingletonQuerysetMixin, viewsets.ModelViewSet): permission_classes = [IsAdminUser] - serializer_class = ShopConfigurationAdminSerializer + serializer_class = SiteConfigurationAdminSerializer -class ShopConfigurationPublicViewSet(_SingletonQuerysetMixin, viewsets.ReadOnlyModelViewSet): +class SiteConfigurationPublicViewSet(_SingletonQuerysetMixin, viewsets.ReadOnlyModelViewSet): permission_classes = [AllowAny] - serializer_class = ShopConfigurationPublicSerializer \ No newline at end of file + serializer_class = SiteConfigurationPublicSerializer \ No newline at end of file diff --git a/backend/templates/email/base.html b/backend/templates/email/components/base.html similarity index 100% rename from backend/templates/email/base.html rename to backend/templates/email/components/base.html diff --git a/backend/thirdparty/zasilkovna/models.py b/backend/thirdparty/zasilkovna/models.py index 174911e..0dee6f6 100644 --- a/backend/thirdparty/zasilkovna/models.py +++ b/backend/thirdparty/zasilkovna/models.py @@ -26,7 +26,7 @@ from rest_framework.exceptions import ValidationError from .client import PacketaAPI -from configuration.models import ShopConfiguration +from configuration.models import SiteConfiguration packeta_client = PacketaAPI() # single reusable instance @@ -103,8 +103,8 @@ class ZasilkovnaPacket(models.Model): cod=order.total_price if cash_on_delivery else 0, # dobírka value=order.total_price, - currency=ShopConfiguration.get_solo().currency, #CZK - eshop= ShopConfiguration.get_solo().name, + currency=SiteConfiguration.get_solo().currency, #CZK + eSite= SiteConfiguration.get_solo().name, ) self.packet_id = response['packet_id'] self.barcode = response['barcode'] diff --git a/backend/thirdparty/zasilkovna/views.py b/backend/thirdparty/zasilkovna/views.py index 333fd28..54c3797 100644 --- a/backend/thirdparty/zasilkovna/views.py +++ b/backend/thirdparty/zasilkovna/views.py @@ -5,7 +5,7 @@ from django.template import loader from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse, OpenApiParameter, OpenApiTypes -from backend.configuration.models import ShopConfiguration +from backend.configuration.models import SiteConfiguration from .models import ZasilkovnaShipment, ZasilkovnaPacket from .serializers import ( @@ -135,7 +135,7 @@ class ZasilkovnaPacketViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet widget_html = loader.render_to_string( "zasilkovna/pickup_point_widget.html", { - "api_key": ShopConfiguration.get_solo().zasilkovna_widget_api_key, + "api_key": SiteConfiguration.get_solo().zasilkovna_widget_api_key, } ) diff --git a/backend/vontor_cz/urls.py b/backend/vontor_cz/urls.py index b797976..53a3593 100644 --- a/backend/vontor_cz/urls.py +++ b/backend/vontor_cz/urls.py @@ -37,7 +37,7 @@ urlpatterns = [ path('api/account/', include('account.urls')), path('api/commerce/', include('commerce.urls')), path('api/configuration/', include('configuration.urls')), - #path('api/advertisments/', include('advertisements.urls')), + path('api/advertisement/', include('advertisement.urls')), path('api/stripe/', include('thirdparty.stripe.urls')), path('api/trading212/', include('thirdparty.trading212.urls')), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a8832f4..f5c8d2b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,7 +7,6 @@ import PrivateRoute from "./routes/PrivateRoute"; // Pages import PortfolioPage from "./pages/portfolio/PortfolioPage"; -import HostingSecurityPage from "./pages/hosting/HostingSecurityPage"; import ContactPage from "./pages/contact/ContactPage"; import ScrollToTop from "./components/common/ScrollToTop"; @@ -21,7 +20,6 @@ export default function App() { }> } /> } /> - } /> } /> {/* Utilities */} diff --git a/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx b/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx index b4f1b1c..ff4387b 100644 --- a/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx +++ b/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx @@ -73,6 +73,7 @@ export default function ContactMeForm() { placeholder="Vaše zpráva" required /> + diff --git a/frontend/src/components/hero/HeroCarousel.tsx b/frontend/src/components/hero/HeroCarousel.tsx deleted file mode 100644 index b2ec490..0000000 --- a/frontend/src/components/hero/HeroCarousel.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useEffect, useState } from "react"; - -export default function HeroCarousel() { - return ( - <> - - ); -} \ No newline at end of file diff --git a/frontend/src/components/hosting/HostingSecuritySection.tsx b/frontend/src/components/hosting/HostingSecuritySection.tsx deleted file mode 100644 index 5c30646..0000000 --- a/frontend/src/components/hosting/HostingSecuritySection.tsx +++ /dev/null @@ -1,21 +0,0 @@ - -export default function HostingSecuritySection() { - return ( -
-
-

Hosting & Protection

-
-

We host our applications ourselves, which reduces hosting costs as projects scale.

-

All websites are protected by Cloudflare and optimized for performance.

-
- {['Server', 'Cloudflare', 'Docker', 'SSL', 'Monitoring', 'Scaling'].map(item => ( -
- {item} -
- ))} -
-
-
-
- ); -} \ No newline at end of file diff --git a/frontend/src/components/navbar/SiteNav.tsx b/frontend/src/components/navbar/SiteNav.tsx index e84daf1..ba01e9a 100644 --- a/frontend/src/components/navbar/SiteNav.tsx +++ b/frontend/src/components/navbar/SiteNav.tsx @@ -113,7 +113,6 @@ export default function Navbar({ user, onLogin, onLogout }: NavbarProps) { Kontakt - Projekty {/* right: user area */} {!user ? ( @@ -136,9 +135,9 @@ export default function Navbar({ user, onLogin, onLogout }: NavbarProps) {
- Profil - Nastavení - Platby + Profil + Nastavení + Platby