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