-
- {t('common.actions.loading')}
+
+
+
+
+
{t('help.article.loadingTitle')}
+
{t('help.article.loadingDescription')}
+
+
+
)}
@@ -77,11 +92,6 @@ export default function HelpArticlePage() {
{article.updated_at && (
{t('help.article.updated', { date: formatDate(article.updated_at, locale) })}
)}
-
('loading');
const [servedFromCache, setServedFromCache] = React.useState(false);
+ const [isOnline, setIsOnline] = React.useState(() => (typeof navigator !== 'undefined' ? navigator.onLine : true));
const basePath = params.token ? `/e/${encodeURIComponent(params.token)}/help` : '/help';
const loadArticles = React.useCallback(async (forceRefresh = false) => {
@@ -37,6 +38,24 @@ export default function HelpCenterPage() {
loadArticles();
}, [loadArticles]);
+ React.useEffect(() => {
+ if (typeof window === 'undefined') {
+ return;
+ }
+
+ const handleOnline = () => setIsOnline(true);
+ const handleOffline = () => setIsOnline(false);
+ window.addEventListener('online', handleOnline);
+ window.addEventListener('offline', handleOffline);
+
+ return () => {
+ window.removeEventListener('online', handleOnline);
+ window.removeEventListener('offline', handleOffline);
+ };
+ }, []);
+
+ const showOfflineBadge = servedFromCache && !isOnline;
+
const filteredArticles = React.useMemo(() => {
if (!query.trim()) {
return articles;
@@ -85,7 +104,7 @@ export default function HelpCenterPage() {
)}
- {servedFromCache && (
+ {showOfflineBadge && (
{t('help.center.offlineBadge')}
diff --git a/resources/js/guest/pages/__tests__/HelpArticlePage.test.tsx b/resources/js/guest/pages/__tests__/HelpArticlePage.test.tsx
new file mode 100644
index 0000000..fc19d20
--- /dev/null
+++ b/resources/js/guest/pages/__tests__/HelpArticlePage.test.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { describe, expect, it, vi } from 'vitest';
+import { render, screen, waitFor } from '@testing-library/react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import HelpArticlePage from '../HelpArticlePage';
+import type { HelpArticleDetail } from '../../services/helpApi';
+
+vi.mock('../../i18n/LocaleContext', () => ({
+ useLocale: () => ({ locale: 'de' }),
+}));
+
+vi.mock('../../i18n/useTranslation', () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}));
+
+vi.mock('../../services/helpApi', () => ({
+ getHelpArticle: vi.fn(),
+}));
+
+const { getHelpArticle } = await import('../../services/helpApi');
+
+describe('HelpArticlePage', () => {
+ it('renders a single back button after loading', async () => {
+ const article: HelpArticleDetail = {
+ slug: 'gallery-and-sharing',
+ title: 'Galerie & Teilen',
+ summary: 'Kurzfassung',
+ body_html: 'Inhalt
',
+ };
+
+ (getHelpArticle as ReturnType).mockResolvedValue({ article, servedFromCache: false });
+
+ render(
+
+
+ } />
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('Galerie & Teilen')).toBeInTheDocument();
+ });
+
+ expect(screen.getAllByText('help.article.back')).toHaveLength(1);
+ });
+});