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')} -

+