Skip to content

Commit c2075ea

Browse files
authored
Version 0.44.0 (#1041)
- dependency updates - fix issues with preview in admin panel - improved error tracking - bug fixes
2 parents ad1767a + f9b6e37 commit c2075ea

File tree

21 files changed

+1130
-2892
lines changed

21 files changed

+1130
-2892
lines changed

package.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "conveniat27",
3-
"version": "0.43.0",
3+
"version": "0.44.0",
44
"description": "The official website of conveniat27",
55
"license": "MIT",
66
"author": "Cyrill Püntener v/o JPG (cyrill.puentener@cevi.ch)",
@@ -66,17 +66,17 @@
6666
"@opentelemetry/sdk-trace-base": "^2.5.1",
6767
"@opentelemetry/sdk-trace-node": "^2.5.1",
6868
"@opentelemetry/semantic-conventions": "^1.40.0",
69-
"@payloadcms/db-mongodb": "^3.79.0",
70-
"@payloadcms/email-nodemailer": "^3.79.0",
71-
"@payloadcms/live-preview-react": "^3.79.0",
72-
"@payloadcms/next": "^3.79.0",
73-
"@payloadcms/plugin-form-builder": "^3.79.0",
74-
"@payloadcms/plugin-redirects": "^3.79.0",
75-
"@payloadcms/plugin-search": "^3.79.0",
76-
"@payloadcms/richtext-lexical": "^3.79.0",
77-
"@payloadcms/richtext-slate": "^3.79.0",
78-
"@payloadcms/storage-s3": "^3.79.0",
79-
"@payloadcms/ui": "^3.79.0",
69+
"@payloadcms/db-mongodb": "^3.79.1",
70+
"@payloadcms/email-nodemailer": "^3.79.1",
71+
"@payloadcms/live-preview-react": "^3.79.1",
72+
"@payloadcms/next": "^3.79.1",
73+
"@payloadcms/plugin-form-builder": "^3.79.1",
74+
"@payloadcms/plugin-redirects": "^3.79.1",
75+
"@payloadcms/plugin-search": "^3.79.1",
76+
"@payloadcms/richtext-lexical": "^3.79.1",
77+
"@payloadcms/richtext-slate": "^3.79.1",
78+
"@payloadcms/storage-s3": "^3.79.1",
79+
"@payloadcms/ui": "^3.79.1",
8080
"@posthog/nextjs-config": "^1.8.18",
8181
"@prisma/adapter-pg": "7.4.2",
8282
"@prisma/client": "7.4.2",
@@ -125,7 +125,7 @@
125125
"next-devtools-mcp": "^0.3.10",
126126
"next-i18n-router": "^5.5.6",
127127
"node-pop3": "^0.11.0",
128-
"payload": "^3.79.0",
128+
"payload": "^3.79.1",
129129
"posthog-js": "^1.356.1",
130130
"posthog-node": "^4.18.0",
131131
"qs-esm": "^7.0.3",
@@ -208,7 +208,7 @@
208208
"undici": "^7.22.0",
209209
"unrs-resolver": "^1.11.1"
210210
},
211-
"packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
211+
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
212212
"engines": {
213213
"node": ">=24.1.0"
214214
},

pnpm-lock.yaml

Lines changed: 114 additions & 114 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Locale, StaticTranslationString } from '@/types/types';
2+
import { i18nConfig } from '@/types/types';
3+
import { DesignCodes } from '@/utils/design-codes';
4+
import { Loader2 } from 'lucide-react';
5+
import React from 'react';
6+
7+
const loadingText: StaticTranslationString = {
8+
de: 'Generiere Vorschau...',
9+
en: 'Generating preview...',
10+
fr: "Génération de l'aperçu...",
11+
};
12+
13+
export function generateStaticParams(): { locale: string; design: string }[] {
14+
const designs = Object.values(DesignCodes);
15+
return designs.flatMap((design) => i18nConfig.locales.map((locale) => ({ locale, design })));
16+
}
17+
18+
export default async function PreviewFallbackPage({
19+
params,
20+
}: {
21+
params: Promise<{ locale: Locale; design: DesignCodes }>;
22+
}): Promise<React.JSX.Element> {
23+
const { locale } = await params;
24+
const label = loadingText[locale];
25+
26+
return (
27+
<div className="flex h-screen flex-col items-center justify-center gap-4 bg-gray-50 text-gray-500">
28+
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
29+
<span className="text-sm font-medium">{label}</span>
30+
</div>
31+
);
32+
}

src/app/(payload)/admin/importMap.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/global-error.tsx

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,47 @@
11
'use client'; // Error boundaries must be Client Components
22

33
import type React from 'react';
4-
import { useEffect } from 'react';
4+
import { useEffect, useState } from 'react';
55

66
import '@/app/globals.scss';
77
import { OnboardingLayout } from '@/features/onboarding/components/onboarding-layout';
88
import { isDraftOrPreviewMode } from '@/utils/draft-mode';
99

10+
const DRAFT_MODE_RELOAD_DELAY_MS = 2000;
11+
1012
/**
1113
* This file is responsible for converting a general runtime error page.
1214
*
1315
* If this page happens, there was an uncaught error in the root layout of the app;
1416
* normally this should never happen.
1517
*
18+
* In draft/preview mode (Payload CMS Live Preview), transient network errors
19+
* are expected when the server briefly restarts. Instead of showing an error page
20+
* (which permanently breaks the iframe), we auto-reload after a short delay.
21+
*
1622
* @param error
1723
* @constructor
1824
*/
1925
const GlobalError: React.FC<{
2026
error: Error & { digest?: string };
2127
}> = ({ error }) => {
28+
const [isRecovering, setIsRecovering] = useState(false);
29+
2230
useEffect(() => {
23-
// Skip offline redirect in draft mode (Payload admin panel) or preview mode (?preview=true)
24-
// In these modes, errors should be handled by the backend
31+
// In draft/preview mode, auto-reload instead of showing the error page.
32+
// This prevents the Payload CMS Live Preview iframe from getting stuck.
2533
if (isDraftOrPreviewMode()) {
26-
console.error('[GlobalError] Error in draft mode, not redirecting to offline page:', error);
27-
return;
34+
console.warn(
35+
'[GlobalError] Transient error in draft/preview mode. Auto-reloading in 2s:',
36+
error.message,
37+
);
38+
setIsRecovering(true);
39+
const timer = setTimeout((): void => {
40+
globalThis.location.reload();
41+
}, DRAFT_MODE_RELOAD_DELAY_MS);
42+
return (): void => {
43+
clearTimeout(timer);
44+
};
2845
}
2946

3047
// Check if the error is likely due to being offline (e.g., failed to load a JS chunk)
@@ -58,8 +75,22 @@ const GlobalError: React.FC<{
5875
})
5976
.catch((error_: unknown) => console.error('Failed to capture error with PostHog', error_));
6077
}
78+
return;
6179
}, [error]);
6280

81+
// In draft/preview mode, show a minimal recovery message instead of the full error page
82+
if (isRecovering) {
83+
return (
84+
<html>
85+
<body>
86+
<div className="flex h-dvh w-dvw flex-col items-center justify-center bg-gray-50 p-4">
87+
<p className="text-gray-500">Verbindung wird wiederhergestellt</p>
88+
</div>
89+
</body>
90+
</html>
91+
);
92+
}
93+
6394
return (
6495
<html>
6596
<body>

src/components/utils/refresh-preview.tsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,47 @@
22

33
import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react';
44
import { useRouter } from 'next/navigation';
5-
import React from 'react';
5+
import React, { useCallback, useEffect, useRef } from 'react';
66

7+
/**
8+
* Returns a debounced refresh handler that calls `router.refresh()` after the specified delay.
9+
* Any pending timeout is cleared when the component using this hook unmounts.
10+
*
11+
* @param delayMs - Debounce delay in milliseconds before refreshing the route.
12+
* @returns A debounced refresh callback.
13+
*/
14+
function useDebouncedRouteRefresh(delayMs: number): () => void {
15+
const router = useRouter();
16+
const timerReference = useRef<NodeJS.Timeout | null>(null);
17+
18+
useEffect(() => {
19+
return (): void => {
20+
if (timerReference.current) {
21+
clearTimeout(timerReference.current);
22+
}
23+
};
24+
}, []);
25+
26+
const handleRefresh = useCallback(() => {
27+
if (timerReference.current) {
28+
clearTimeout(timerReference.current);
29+
}
30+
// Delay the actual refresh by the configured debounce duration to prevent overwhelming the server
31+
// with RSC queries and hitting Traefik/Next.js concurrent stream limit timeouts.
32+
timerReference.current = setTimeout(() => {
33+
router.refresh();
34+
}, delayMs);
35+
}, [router, delayMs]);
36+
37+
return handleRefresh;
38+
}
39+
40+
/**
41+
* Client component that wires Payload live preview to a debounced Next.js route refresh.
42+
*/
743
export const RefreshRouteOnSave: React.FC<{
844
serverURL: string;
945
}> = ({ serverURL }) => {
10-
const router = useRouter();
11-
12-
return <PayloadLivePreview refresh={() => router.refresh()} serverURL={serverURL} />;
46+
const handleRefresh = useDebouncedRouteRefresh(750);
47+
return <PayloadLivePreview refresh={handleRefresh} serverURL={serverURL} />;
1348
};

src/features/payload-cms/api/cached-blogs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ export const getBlogArticleBySlugCached = cache(
7676
},
7777
});
7878

79-
return { docs: result.docs };
79+
// deduplicate by id in case of internal payload cms duplicate bugs
80+
const uniqueDocuments = [
81+
...new Map(result.docs.map((document_) => [document_.id, document_])).values(),
82+
];
83+
84+
return { docs: uniqueDocuments };
8085
});
8186
},
8287
);

src/features/payload-cms/api/cached-generic-pages.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ export const getGenericPageBySlugCached = cache(
3434
},
3535
});
3636

37-
return { docs: result.docs };
37+
// deduplicate by id in case of internal payload cms duplicate bugs
38+
const uniqueDocuments = [
39+
...new Map(result.docs.map((document_) => [document_.id, document_])).values(),
40+
];
41+
42+
return { docs: uniqueDocuments };
3843
});
3944
},
4045
);

src/features/payload-cms/page-layouts/blog-posts.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ const BlogPostPage: LocalizedCollectionComponent = async ({
5151
renderInPreviewMode,
5252
);
5353

54-
if (articlesInPrimaryLanguage.docs.length > 1)
55-
throw new Error('More than one article with the same slug found');
54+
if (articlesInPrimaryLanguage.docs.length > 1) {
55+
const conflicting = articlesInPrimaryLanguage.docs
56+
.map((document_) => `id=${document_.id}, internalPageName="${document_.internalPageName}"`)
57+
.join('; ');
58+
throw new Error(
59+
`More than one article with the same slug found (slug="${slug}", locale="${locale}"). Conflicting pages: [${conflicting}]`,
60+
);
61+
}
5662

5763
const articleInPrimaryLanguage = articlesInPrimaryLanguage.docs[0];
5864

src/features/payload-cms/page-layouts/generic-page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,14 @@ const GenericPage: LocalizedCollectionComponent = async ({
5959
renderInPreviewMode,
6060
);
6161

62-
if (articlesInPrimaryLanguage.docs.length > 1)
63-
throw new Error('More than one article with the same slug found');
62+
if (articlesInPrimaryLanguage.docs.length > 1) {
63+
const conflicting = articlesInPrimaryLanguage.docs
64+
.map((document_) => `id=${document_.id}, internalPageName="${document_.internalPageName}"`)
65+
.join('; ');
66+
throw new Error(
67+
`More than one article with the same slug found (slug="${slug}", locale="${locale}"). Conflicting pages: [${conflicting}]`,
68+
);
69+
}
6470

6571
const articleInPrimaryLanguage = articlesInPrimaryLanguage.docs[0];
6672

0 commit comments

Comments
 (0)