From 3bb4f1a1a894a6bbc4b1610c3585b73e38f1753d Mon Sep 17 00:00:00 2001
From: Lutthy <22339795+lbossuyt@users.noreply.github.com>
Date: Sun, 9 Aug 2026 11:16:18 +0200
Subject: [PATCH] fix(branding): hide "Powered by PicPeak" on every page, not
only the gallery (#999)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
branding_hide_powered_by only hid the attribution on the main gallery footer. It
stayed visible on the gallery password screen, client access page, Premium
layout, admin and customer login, accept-invite and CMS pages — AdminLoginPage
rendered it unconditionally with no guard at all, so the setting genuinely did
not apply there.
Routes those surfaces through one component in components/common
that reads the public setting itself (the DynamicFavicon pattern) and renders
nothing when white-labeling is on, including while the settings are still
loading so a white-labelled instance never flashes the attribution.
Also collapses three duplicate translation keys (gallery.poweredBy,
adminLogin.poweredBy, customer.login.poweredBy) into a single common.poweredBy,
and translates pages that had 'Powered by' hardcoded in English across all 8
locales.
Fork-PR workflows were never approved so CI did not run. Verified locally
against cf243b44: tsc --noEmit clean, ESLint clean, vitest 124 passed across 24
files, and npm run build succeeds.
GalleryLayout.tsx keeps its own inline guard and is not routed through the new
component; tracked separately.
Co-authored-by: lbossuyt
---
.../src/components/common/CMSContentBlock.tsx | 7 +--
frontend/src/components/common/PoweredBy.tsx | 25 ++++++++
.../common/__tests__/PoweredBy.test.tsx | 63 +++++++++++++++++++
frontend/src/components/common/index.ts | 1 +
.../gallery/layouts/GalleryPremiumLayout.tsx | 4 +-
frontend/src/i18n/locales/de.json | 10 ++-
frontend/src/i18n/locales/en.json | 8 +--
frontend/src/i18n/locales/es.json | 4 +-
frontend/src/i18n/locales/fr.json | 8 +--
frontend/src/i18n/locales/nl.json | 10 ++-
frontend/src/i18n/locales/pt.json | 10 ++-
frontend/src/i18n/locales/ru.json | 10 ++-
frontend/src/i18n/locales/sl.json | 8 +--
frontend/src/pages/ClientAccessPage.tsx | 6 +-
frontend/src/pages/GalleryPage.tsx | 10 +--
frontend/src/pages/admin/AdminLoginPage.tsx | 6 +-
.../src/pages/customer/CustomerLoginPage.tsx | 6 +-
.../src/pages/public/AcceptInvitePage.tsx | 6 +-
18 files changed, 131 insertions(+), 71 deletions(-)
create mode 100644 frontend/src/components/common/PoweredBy.tsx
create mode 100644 frontend/src/components/common/__tests__/PoweredBy.test.tsx
diff --git a/frontend/src/components/common/CMSContentBlock.tsx b/frontend/src/components/common/CMSContentBlock.tsx
index d822e1de..1fae596b 100644
--- a/frontend/src/components/common/CMSContentBlock.tsx
+++ b/frontend/src/components/common/CMSContentBlock.tsx
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import DOMPurify from 'dompurify';
import { Card } from './Card';
import { Loading } from './Loading';
+import { PoweredBy } from './PoweredBy';
import { cmsService } from '../../services/cms.service';
import { usePublicSettings } from '../../hooks/usePublicSettings';
import { buildResourceUrl } from '../../utils/url';
@@ -132,11 +133,7 @@ export const CMSContentBlock: React.FC = ({ slug, fallback
{lang === 'de' ? 'Datenschutz' : 'Privacy Policy'}
- {!settings?.branding_hide_powered_by && (
-
- Powered by PicPeak
-
- )}
+
);
diff --git a/frontend/src/components/common/PoweredBy.tsx b/frontend/src/components/common/PoweredBy.tsx
new file mode 100644
index 00000000..68b3fabf
--- /dev/null
+++ b/frontend/src/components/common/PoweredBy.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { usePublicSettings } from '../../hooks/usePublicSettings';
+
+interface PoweredByProps {
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+// "Powered by PicPeak" footer/login attribution. Reads the setting itself (like
+// DynamicFavicon) so callers don't repeat the guard; hidden for white-label.
+export const PoweredBy: React.FC = ({ className, style }) => {
+ const { t } = useTranslation();
+ const { data: settings } = usePublicSettings();
+
+ // Hide while settings are still loading too, otherwise a white-labelled
+ // instance flashes the attribution before branding_hide_powered_by resolves.
+ if (!settings || settings.branding_hide_powered_by) return null;
+
+ return (
+
+ {t('common.poweredBy')} PicPeak
+
+ );
+};
diff --git a/frontend/src/components/common/__tests__/PoweredBy.test.tsx b/frontend/src/components/common/__tests__/PoweredBy.test.tsx
new file mode 100644
index 00000000..c67eea26
--- /dev/null
+++ b/frontend/src/components/common/__tests__/PoweredBy.test.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { vi, describe, it, expect, beforeEach } from 'vitest';
+import { PoweredBy } from '../PoweredBy';
+import { usePublicSettings } from '../../../hooks/usePublicSettings';
+
+// stub i18n so the prefix comes back as plain English for the assertions
+vi.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key: string) => (key === 'common.poweredBy' ? 'Powered by' : key),
+ }),
+}));
+
+vi.mock('../../../hooks/usePublicSettings', () => ({
+ usePublicSettings: vi.fn(),
+}));
+
+const mockUsePublicSettings = vi.mocked(usePublicSettings);
+
+const setSettings = (data: Record | undefined) => {
+ mockUsePublicSettings.mockReturnValue({ data } as never);
+};
+
+describe('PoweredBy', () => {
+ beforeEach(() => {
+ mockUsePublicSettings.mockReset();
+ });
+
+ it('renders the "Powered by PicPeak" attribution by default', () => {
+ setSettings({});
+ render( );
+ expect(screen.getByText(/Powered by/)).toBeInTheDocument();
+ expect(screen.getByText('PicPeak')).toBeInTheDocument();
+ });
+
+ it('renders nothing while settings are still loading (no attribution flash)', () => {
+ setSettings(undefined);
+ const { container } = render( );
+ expect(container).toBeEmptyDOMElement();
+ expect(screen.queryByText('PicPeak')).not.toBeInTheDocument();
+ });
+
+ it('renders nothing when branding_hide_powered_by is enabled (white-label)', () => {
+ setSettings({ branding_hide_powered_by: true });
+ const { container } = render( );
+ expect(container).toBeEmptyDOMElement();
+ expect(screen.queryByText('PicPeak')).not.toBeInTheDocument();
+ });
+
+ it('renders when branding_hide_powered_by is explicitly false', () => {
+ setSettings({ branding_hide_powered_by: false });
+ render( );
+ expect(screen.getByText('PicPeak')).toBeInTheDocument();
+ });
+
+ it('forwards className and style to the wrapping paragraph', () => {
+ setSettings({});
+ render( );
+ const paragraph = screen.getByText('PicPeak').closest('p');
+ expect(paragraph).toHaveClass('text-xs', 'mt-2');
+ expect(paragraph).toHaveStyle({ opacity: '0.5' });
+ });
+});
diff --git a/frontend/src/components/common/index.ts b/frontend/src/components/common/index.ts
index f68369aa..20441b0c 100644
--- a/frontend/src/components/common/index.ts
+++ b/frontend/src/components/common/index.ts
@@ -29,4 +29,5 @@ export { ProtectionWarning } from './ProtectionWarning';
export { ReCaptcha } from './ReCaptcha';
export { PasswordGenerator } from './PasswordGenerator';
export { MarkdownContent } from './MarkdownContent';
+export { PoweredBy } from './PoweredBy';
export { ConfirmDialogProvider, useConfirm, type ConfirmOptions, type ConfirmVariant } from './ConfirmDialog';
diff --git a/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx b/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx
index 226737e2..8261d4c5 100644
--- a/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx
+++ b/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx
@@ -18,7 +18,7 @@ import { useInView } from 'react-intersection-observer';
import type { BaseGalleryLayoutProps } from './BaseGalleryLayout';
import type { Photo } from '../../../types';
-import { AuthenticatedImage } from '../../common';
+import { AuthenticatedImage, PoweredBy } from '../../common';
import { feedbackService } from '../../../services/feedback.service';
import { PhotoReactions } from '../PhotoReactions';
import { useGuestIdentityOptional } from '../../../contexts/GuestIdentityContext';
@@ -598,7 +598,7 @@ export const GalleryPremiumLayout: React.FC = ({
{/* Footer */}
- {t('gallery.poweredBy', 'Powered by PicPeak')}
+
© {new Date().getFullYear()} {t('gallery.allRightsReserved', 'All rights reserved')}
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index c2bab2ca..ec008460 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -152,7 +152,8 @@
"duplicate": "Duplizieren",
"showAll": "Alle anzeigen",
"confirm": "Bestätigen",
- "show": "Einblenden"
+ "show": "Einblenden",
+ "poweredBy": "Bereitgestellt von"
},
"upload": {
"photoCategory": "Fotokategorie",
@@ -936,8 +937,7 @@
"socials": "Soziale Netzwerke"
},
"photosCount_one": "{{count}} Foto",
- "photosCount_other": "{{count}} Fotos",
- "poweredBy": "Bereitgestellt von PicPeak"
+ "photosCount_other": "{{count}} Fotos"
},
"categories": {
"title": "Fotokategorien",
@@ -3792,7 +3792,6 @@
"invalidCredentials": "Ungültige E-Mail oder Passwort",
"generalError": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
"needHelp": "Hilfe benötigt? Kontakt",
- "poweredBy": "Bereitgestellt von PicPeak",
"devModeHint": "Entwicklungsmodus: E-Mail: admin@example.com, Passwort: admin123",
"mfa": {
"title": "Zwei-Faktor-Authentifizierung",
@@ -3889,8 +3888,7 @@
"adminHint": "Sie suchen das Admin-Panel? Besuchen Sie /admin/login.",
"emailPlaceholder": "du@beispiel.de",
"passwordPlaceholder": "Dein Passwort",
- "needHelp": "Brauchst du Hilfe?",
- "poweredBy": "Bereitgestellt von PicPeak"
+ "needHelp": "Brauchst du Hilfe?"
},
"acceptInvite": {
"title": "Konto einrichten",
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 6174abd6..dbedf69d 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -152,7 +152,8 @@
"duplicate": "Duplicate",
"showAll": "Show all",
"confirm": "Confirm",
- "show": "Show"
+ "show": "Show",
+ "poweredBy": "Powered by"
},
"upload": {
"photoCategory": "Photo Category",
@@ -478,7 +479,6 @@
},
"photosCount_one": "{{count}} photo",
"photosCount_other": "{{count}} photos",
- "poweredBy": "Powered by PicPeak",
"photosSelected_one": "{{count}} photo selected",
"photosSelected_other": "{{count}} photos selected",
"downloadSelected_one": "Download {{count}} photo",
@@ -3678,7 +3678,6 @@
"invalidCredentials": "Invalid email or password",
"generalError": "An error occurred. Please try again.",
"needHelp": "Need help? Contact",
- "poweredBy": "Powered by PicPeak",
"devModeHint": "Development Mode: Use email: admin@example.com, password: admin123",
"mfa": {
"title": "Two-factor authentication",
@@ -3889,8 +3888,7 @@
"adminHint": "Looking for the admin panel? Visit /admin/login.",
"emailPlaceholder": "you@example.com",
"passwordPlaceholder": "Your password",
- "needHelp": "Need help?",
- "poweredBy": "Powered by PicPeak"
+ "needHelp": "Need help?"
},
"acceptInvite": {
"title": "Set up your account",
diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json
index e4a48a8b..5a33a09d 100644
--- a/frontend/src/i18n/locales/es.json
+++ b/frontend/src/i18n/locales/es.json
@@ -119,7 +119,8 @@
"select": "Seleccionar",
"selected": "Seleccionado",
"chunk": "Fragmento",
- "optional": "opcional"
+ "optional": "opcional",
+ "poweredBy": "Desarrollado por"
},
"upload": {
"photoCategory": "Categoría de foto",
@@ -2458,7 +2459,6 @@
"invalidCredentials": "Correo o contraseña inválidos",
"generalError": "Ocurrió un error. Por favor, inténtalo de nuevo.",
"needHelp": "¿Necesitas ayuda? Contacta",
- "poweredBy": "Desarrollado por PicPeak",
"devModeHint": "Modo desarrollo: usa admin@example.com, contraseña: admin123"
},
"clientAccess": {
diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json
index 1b30f209..4f1dbdfe 100644
--- a/frontend/src/i18n/locales/fr.json
+++ b/frontend/src/i18n/locales/fr.json
@@ -124,7 +124,8 @@
"saveChanges": "Enregistrer les modifications",
"resetChanges": "Réinitialiser les modifications",
"dismiss": "Ignorer",
- "discard": "Abandonner"
+ "discard": "Abandonner",
+ "poweredBy": "Propulsé par"
},
"upload": {
"photoCategory": "Catégorie de la photo",
@@ -334,7 +335,6 @@
},
"photosCount_one": "{{count}} photo",
"photosCount_other": "{{count}} photos",
- "poweredBy": "Propulsé par PicPeak",
"photosSelected_one": "{{count}} photo sélectionnée",
"photosSelected_other": "{{count}} photos sélectionnées",
"downloadSelected_one": "Télécharger {{count}} photo",
@@ -2682,7 +2682,6 @@
"invalidCredentials": "E-mail ou mot de passe invalide",
"generalError": "Une erreur est survenue. Veuillez réessayer.",
"needHelp": "Besoin d'aide ? Contactez",
- "poweredBy": "Propulsé par PicPeak",
"devModeHint": "Mode développement : Utilisez l'e-mail : admin@example.com, mot de passe : admin123"
},
"clientAccess": {
@@ -2762,8 +2761,7 @@
"adminHint": "Vous cherchez le panneau d'administration ? Visitez /admin/login.",
"emailPlaceholder": "vous@example.com",
"passwordPlaceholder": "Votre mot de passe",
- "needHelp": "Besoin d'aide ?",
- "poweredBy": "Propulsé par PicPeak"
+ "needHelp": "Besoin d'aide ?"
},
"acceptInvite": {
"title": "Configurer votre compte",
diff --git a/frontend/src/i18n/locales/nl.json b/frontend/src/i18n/locales/nl.json
index 77da18b9..28ac697b 100644
--- a/frontend/src/i18n/locales/nl.json
+++ b/frontend/src/i18n/locales/nl.json
@@ -124,7 +124,8 @@
"saveChanges": "Wijzigingen opslaan",
"resetChanges": "Wijzigingen ongedaan maken",
"dismiss": "Sluiten",
- "discard": "Verwerpen"
+ "discard": "Verwerpen",
+ "poweredBy": "Mogelijk gemaakt door"
},
"upload": {
"photoCategory": "Fotocategorie",
@@ -337,8 +338,7 @@
"socials": "Sociale media"
},
"photosCount_one": "{{count}} foto",
- "photosCount_other": "{{count}} foto's",
- "poweredBy": "Mogelijk gemaakt door PicPeak"
+ "photosCount_other": "{{count}} foto's"
},
"categories": {
"title": "Fotocategorieen",
@@ -2680,7 +2680,6 @@
"invalidCredentials": "Ongeldige e-mail of wachtwoord",
"generalError": "Er is een fout opgetreden. Probeer het opnieuw.",
"needHelp": "Hulp nodig? Neem contact op met",
- "poweredBy": "Powered by PicPeak",
"devModeHint": "Ontwikkelmodus: Gebruik e-mail: admin@example.com, wachtwoord: admin123"
},
"clientAccess": {
@@ -2751,8 +2750,7 @@
"adminHint": "Op zoek naar het beheerderspaneel? Ga naar /admin/login.",
"emailPlaceholder": "jij@voorbeeld.com",
"passwordPlaceholder": "Je wachtwoord",
- "needHelp": "Hulp nodig?",
- "poweredBy": "Mogelijk gemaakt door PicPeak"
+ "needHelp": "Hulp nodig?"
},
"acceptInvite": {
"title": "Account aanmaken",
diff --git a/frontend/src/i18n/locales/pt.json b/frontend/src/i18n/locales/pt.json
index 85aeed57..43e3c302 100644
--- a/frontend/src/i18n/locales/pt.json
+++ b/frontend/src/i18n/locales/pt.json
@@ -124,7 +124,8 @@
"saveChanges": "Salvar alterações",
"resetChanges": "Repor alterações",
"dismiss": "Dispensar",
- "discard": "Descartar"
+ "discard": "Descartar",
+ "poweredBy": "Desenvolvido por"
},
"upload": {
"photoCategory": "Categoria da Foto",
@@ -345,8 +346,7 @@
},
"photosCount_many": "{{count}} fotos",
"photosCount_one": "{{count}} foto",
- "photosCount_other": "{{count}} fotos",
- "poweredBy": "Desenvolvido por PicPeak"
+ "photosCount_other": "{{count}} fotos"
},
"categories": {
"title": "Categorias de Fotos",
@@ -2701,7 +2701,6 @@
"invalidCredentials": "E-mail ou senha inválidos",
"generalError": "Ocorreu um erro. Tente novamente.",
"needHelp": "Precisa de ajuda? Contate",
- "poweredBy": "Distribuído por PicPeak",
"devModeHint": "Modo Desenvolvimento: E-mail: admin@exemplo.com, Senha: admin123"
},
"clientAccess": {
@@ -2784,8 +2783,7 @@
"adminHint": "Procurando o painel de administração? Acesse /admin/login.",
"emailPlaceholder": "voce@exemplo.com",
"passwordPlaceholder": "Sua senha",
- "needHelp": "Precisa de ajuda?",
- "poweredBy": "Desenvolvido por PicPeak"
+ "needHelp": "Precisa de ajuda?"
},
"acceptInvite": {
"title": "Configure sua conta",
diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json
index 92459cd3..81d5fa65 100644
--- a/frontend/src/i18n/locales/ru.json
+++ b/frontend/src/i18n/locales/ru.json
@@ -124,7 +124,8 @@
"saveChanges": "Сохранить изменения",
"resetChanges": "Сбросить изменения",
"dismiss": "Закрыть",
- "discard": "Отменить"
+ "discard": "Отменить",
+ "poweredBy": "Работает на"
},
"upload": {
"photoCategory": "Категория фото",
@@ -353,8 +354,7 @@
"photosCount_few": "{{count}} фото",
"photosCount_many": "{{count}} фото",
"photosCount_one": "{{count}} фото",
- "photosCount_other": "{{count}} фото",
- "poweredBy": "Работает на PicPeak"
+ "photosCount_other": "{{count}} фото"
},
"categories": {
"title": "Категории фото",
@@ -2731,7 +2731,6 @@
"invalidCredentials": "Неверный email или пароль",
"generalError": "Произошла ошибка. Попробуйте снова.",
"needHelp": "Нужна помощь? Свяжитесь",
- "poweredBy": "Работает на PicPeak",
"devModeHint": "Режим разработки: используйте email: admin@example.com, пароль: admin123"
},
"clientAccess": {
@@ -2817,8 +2816,7 @@
"adminHint": "Ищете админ-панель? Перейдите на /admin/login.",
"emailPlaceholder": "you@example.com",
"passwordPlaceholder": "Ваш пароль",
- "needHelp": "Нужна помощь?",
- "poweredBy": "Работает на PicPeak"
+ "needHelp": "Нужна помощь?"
},
"acceptInvite": {
"title": "Создайте учётную запись",
diff --git a/frontend/src/i18n/locales/sl.json b/frontend/src/i18n/locales/sl.json
index e5f8cde8..a6de41f9 100644
--- a/frontend/src/i18n/locales/sl.json
+++ b/frontend/src/i18n/locales/sl.json
@@ -124,7 +124,8 @@
"saveChanges": "Shrani spremembe",
"resetChanges": "Ponastavi spremembe",
"dismiss": "Zapri",
- "discard": "Zavrzi"
+ "discard": "Zavrzi",
+ "poweredBy": "Poganja"
},
"upload": {
"photoCategory": "Kategorija fotografije",
@@ -334,7 +335,6 @@
},
"photosCount_one": "{{count}} fotografija",
"photosCount_other": "{{count}} fotografij",
- "poweredBy": "Poganja PicPeak",
"photosSelected_one": "Izbrana {{count}} fotografija",
"photosSelected_other": "Izbranih {{count}} fotografij",
"downloadSelected_one": "Prenesi {{count}} fotografijo",
@@ -2671,7 +2671,6 @@
"invalidCredentials": "Neveljavna e-pošta ali geslo",
"generalError": "Prišlo je do napake. Poskusite znova.",
"needHelp": "Potrebujete pomoč? Kontakt",
- "poweredBy": "Poganja PicPeak",
"devModeHint": "Razvojni način: uporabite e-pošto: admin@example.com, geslo: admin123"
},
"clientAccess": {
@@ -2751,8 +2750,7 @@
"adminHint": "Iščete administratorsko ploščo? Obiščite /admin/login.",
"emailPlaceholder": "vi@example.com",
"passwordPlaceholder": "Vaše geslo",
- "needHelp": "Potrebujete pomoč?",
- "poweredBy": "Poganja PicPeak"
+ "needHelp": "Potrebujete pomoč?"
},
"acceptInvite": {
"title": "Nastavite svoj račun",
diff --git a/frontend/src/pages/ClientAccessPage.tsx b/frontend/src/pages/ClientAccessPage.tsx
index 281dd64d..fa258221 100644
--- a/frontend/src/pages/ClientAccessPage.tsx
+++ b/frontend/src/pages/ClientAccessPage.tsx
@@ -3,7 +3,7 @@ import { useParams, useSearchParams, useNavigate, Link } from 'react-router-dom'
import { AlertCircle, Lock } from 'lucide-react';
import { useTranslation } from 'react-i18next';
-import { Card, CardContent, Input, Button, Loading } from '../components/common';
+import { Card, CardContent, Input, Button, Loading, PoweredBy } from '../components/common';
import { useGalleryAuth } from '../contexts';
import { useGalleryInfo } from '../hooks/useGallery';
import { usePublicSettings } from '../hooks/usePublicSettings';
@@ -197,9 +197,7 @@ export const ClientAccessPage: React.FC = () => {
{t('legal.datenschutz')}
-
- Powered by PicPeak
-
+
diff --git a/frontend/src/pages/GalleryPage.tsx b/frontend/src/pages/GalleryPage.tsx
index 929634cf..17a796e9 100644
--- a/frontend/src/pages/GalleryPage.tsx
+++ b/frontend/src/pages/GalleryPage.tsx
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useLocalizedDate } from '../hooks/useLocalizedDate';
import { usePublicSettings } from '../hooks/usePublicSettings';
-import { Card, CardContent, Input, Button, ReCaptcha, CMSContentBlock } from '../components/common';
+import { Card, CardContent, Input, Button, ReCaptcha, CMSContentBlock, PoweredBy } from '../components/common';
import { useGalleryAuth, useTheme } from '../contexts';
import { useGalleryInfo } from '../hooks/useGallery';
import { GalleryView } from '../components/gallery';
@@ -386,9 +386,7 @@ export const GalleryPage: React.FC = () => {
{t('legal.datenschutz')}
-
- Powered by PicPeak
-
+
@@ -594,9 +592,7 @@ export const GalleryPage: React.FC = () => {
{t('legal.datenschutz')}
-
- Powered by PicPeak
-
+
diff --git a/frontend/src/pages/admin/AdminLoginPage.tsx b/frontend/src/pages/admin/AdminLoginPage.tsx
index b650aaff..b7b7aafd 100644
--- a/frontend/src/pages/admin/AdminLoginPage.tsx
+++ b/frontend/src/pages/admin/AdminLoginPage.tsx
@@ -5,7 +5,7 @@ import { Lock, Mail, Eye, EyeOff, AlertCircle, ShieldCheck, KeyRound, ArrowLeft
import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next';
-import { Button, Input, Card, ReCaptcha } from '../../components/common';
+import { Button, Input, Card, ReCaptcha, PoweredBy } from '../../components/common';
import { useAdminAuth } from '../../contexts';
import { authService } from '../../services/auth.service';
import { isMfaChallenge } from '../../types';
@@ -486,9 +486,7 @@ export const AdminLoginPage: React.FC = () => {
{settingsData?.branding_support_email || 'support@example.com'}
-
- {t('adminLogin.poweredBy')}
-
+
{/* Development Hint */}
diff --git a/frontend/src/pages/customer/CustomerLoginPage.tsx b/frontend/src/pages/customer/CustomerLoginPage.tsx
index 391c719d..613afaef 100644
--- a/frontend/src/pages/customer/CustomerLoginPage.tsx
+++ b/frontend/src/pages/customer/CustomerLoginPage.tsx
@@ -10,7 +10,7 @@ import { Lock, Mail, Eye, EyeOff, AlertCircle } from 'lucide-react';
import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next';
-import { Button, Input, Card, ReCaptcha } from '../../components/common';
+import { Button, Input, Card, ReCaptcha, PoweredBy } from '../../components/common';
import { useCustomerAuth } from '../../contexts/CustomerAuthContext';
import { customerService } from '../../services/customer.service';
import { usePublicSettings } from '../../hooks/usePublicSettings';
@@ -261,9 +261,7 @@ export const CustomerLoginPage: React.FC = () => {
{settingsData?.branding_support_email || 'support@example.com'}
-
- {t('customer.login.poweredBy', 'Powered by PicPeak')}
-
+
diff --git a/frontend/src/pages/public/AcceptInvitePage.tsx b/frontend/src/pages/public/AcceptInvitePage.tsx
index be218704..07c7a0b9 100644
--- a/frontend/src/pages/public/AcceptInvitePage.tsx
+++ b/frontend/src/pages/public/AcceptInvitePage.tsx
@@ -14,7 +14,7 @@ import {
} from 'lucide-react';
import { toast } from 'react-toastify';
-import { Button, Input, Card, Loading } from '../../components/common';
+import { Button, Input, Card, Loading, PoweredBy } from '../../components/common';
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
import { api } from '../../config/api';
@@ -537,9 +537,7 @@ export const AcceptInvitePage: React.FC = () => {
{t('acceptInvitation.signIn')}
-
- {t('adminLogin.poweredBy')}
-
+