Skip to content

Commit ad1767a

Browse files
authored
Version 0.43.0 (#1019)
- feat: implement #1016 - feat: capture form errors - fix: Invariant Violation #1012 - fix: suppress #927 - fix: #995 - fix #906 - fix: German preview links #1029 - chore: update dependencies
2 parents f21876a + acd4ccb commit ad1767a

File tree

18 files changed

+608
-321
lines changed

18 files changed

+608
-321
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.42.3",
3+
"version": "0.43.0",
44
"description": "The official website of conveniat27",
55
"license": "MIT",
66
"author": "Cyrill Püntener v/o JPG (cyrill.puentener@cevi.ch)",
@@ -44,7 +44,7 @@
4444
"@aws-sdk/s3-request-presigner": "^3.1000.0",
4545
"@headlessui/react": "^2.2.9",
4646
"@icons-pack/react-simple-icons": "^12.9.0",
47-
"@lexical/selection": "0.35.0",
47+
"@lexical/selection": "0.41.0",
4848
"@mapbox/mapbox-gl-draw": "^1.5.1",
4949
"@next/bundle-analyzer": "16.0.0",
5050
"@opentelemetry/api": "^1.9.0",
@@ -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.78.0",
70-
"@payloadcms/email-nodemailer": "^3.78.0",
71-
"@payloadcms/live-preview-react": "^3.78.0",
72-
"@payloadcms/next": "^3.78.0",
73-
"@payloadcms/plugin-form-builder": "^3.78.0",
74-
"@payloadcms/plugin-redirects": "^3.78.0",
75-
"@payloadcms/plugin-search": "^3.78.0",
76-
"@payloadcms/richtext-lexical": "^3.78.0",
77-
"@payloadcms/richtext-slate": "^3.78.0",
78-
"@payloadcms/storage-s3": "^3.78.0",
79-
"@payloadcms/ui": "^3.78.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",
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.78.0",
128+
"payload": "^3.79.0",
129129
"posthog-js": "^1.356.1",
130130
"posthog-node": "^4.18.0",
131131
"qs-esm": "^7.0.3",

pnpm-lock.yaml

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

src/components/news-card.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '@/features/payload-cms/payload-cms/utils/link-field-logic';
99
import type { Image } from '@/features/payload-cms/payload-types';
1010
import type { Locale } from '@/types/types';
11+
import { cn } from '@/utils/tailwindcss-override';
1112
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical';
1213
import { RichText } from '@payloadcms/richtext-lexical/react';
1314
import ImageNode from 'next/image';
@@ -22,6 +23,7 @@ export interface NewsCardType {
2223
image?: Image;
2324
paragraph?: SerializedEditorState;
2425
locale: Locale;
26+
isSmall?: boolean;
2527
}
2628

2729
export const NewsCardBlock: React.FC<NewsCardType> = ({
@@ -32,11 +34,22 @@ export const NewsCardBlock: React.FC<NewsCardType> = ({
3234
image,
3335
paragraph,
3436
locale,
37+
isSmall = false,
3538
}) => {
3639
const newsCardContent = (
37-
<div className="flex basis-1 flex-col rounded-md border-2 border-gray-200 bg-white p-6 transition duration-200 hover:shadow-md lg:max-w-96">
40+
<div
41+
className={cn(
42+
'flex basis-1 flex-col rounded-md border-2 border-gray-200 bg-white transition duration-200 hover:shadow-md',
43+
isSmall ? 'p-4 lg:max-w-64' : 'p-6 lg:max-w-96',
44+
)}
45+
>
3846
<div>
39-
<span className="font-body text-[12px] font-bold text-gray-500">
47+
<span
48+
className={cn(
49+
'font-body font-bold text-gray-500',
50+
isSmall ? 'text-[10px]' : 'text-[12px]',
51+
)}
52+
>
4053
{new Date(date).toLocaleDateString(locale, {
4154
weekday: 'long',
4255
year: 'numeric',
@@ -45,7 +58,12 @@ export const NewsCardBlock: React.FC<NewsCardType> = ({
4558
timeZone: 'Europe/Zurich',
4659
})}
4760
</span>
48-
<h4 className="font-heading text-conveniat-green mb-6 line-clamp-3 min-h-6 text-base font-extrabold text-ellipsis">
61+
<h4
62+
className={cn(
63+
'font-heading text-conveniat-green mb-6 line-clamp-3 min-h-6 font-extrabold text-ellipsis',
64+
isSmall ? 'text-sm' : 'text-base',
65+
)}
66+
>
4967
{headline}
5068
</h4>
5169
</div>

src/features/next-auth/utils/next-auth-config.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,7 @@ async function refreshAccessToken(token: JWT): Promise<JWT> {
204204
// Update the user in Payload CMS with the new groups
205205
// async loading of payload configuration (to avoid circular dependency)
206206
const config = await import('@payload-config');
207-
// @ts-ignore
208-
const payload = await getPayload({ config });
207+
const payload = await getPayload({ config: config.default });
209208
const payloadCMSUser = await saveAndFetchUserFromPayload(payload, profile);
210209

211210
return {
@@ -317,10 +316,8 @@ export const authOptions: NextAuthConfig = {
317316
if (account && _profile) {
318317
const profile = _profile as unknown as HitobitoProfile;
319318

320-
// async loading of payload configuration (to avoid circular dependency)
321319
const config = await import('@payload-config');
322-
// @ts-ignore
323-
const payload = await getPayload({ config });
320+
const payload = await getPayload({ config: config.default });
324321
const payloadCMSUser = await saveAndFetchUserFromPayload(payload, profile);
325322

326323
return {

src/features/payload-cms/components/content-blocks/timeline-entry.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ export const TimelineEntry: React.FC<{
5050
<SubheadingH2 className="text-md m-0 mt-[-8]">{timeline.title}</SubheadingH2>
5151

5252
<PageSectionsConverter
53-
blocks={timeline.mainContent as unknown as ContentBlock[]}
53+
blocks={
54+
timeline.mainContent?.map((block) =>
55+
block.blockType === 'newsCard' ? { ...block, isSmall: true } : block,
56+
) as unknown as ContentBlock[]
57+
}
5458
locale={locale}
5559
sectionClassName="mt-2"
5660
sectionOverrides={{

src/features/payload-cms/components/form/hooks/use-form-submission.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export const useFormSubmission = ({
139139
if (errorData.errors?.[0]?.data && Array.isArray(errorData.errors[0].data)) {
140140
const fieldErrors = errorData.errors[0].data;
141141
if (setError) {
142+
const capturedErrors: string[] = [];
142143
for (const error of fieldErrors) {
143144
let message = error.message;
144145
switch (message) {
@@ -167,9 +168,16 @@ export const useFormSubmission = ({
167168
break;
168169
}
169170
}
171+
capturedErrors.push(`${error.field}: ${error.message}`);
170172
setError(error.field, { type: 'server', message });
171173
}
172174

175+
posthog.capture('form_validation_failed', {
176+
form_id: formId,
177+
error_message: capturedErrors.join(', '),
178+
source: 'server',
179+
});
180+
173181
// Navigate to the step containing the first errored field
174182
if (formSections && setCurrentStepIndex && fieldErrors.length > 0) {
175183
const firstError = fieldErrors[0];
@@ -208,23 +216,36 @@ export const useFormSubmission = ({
208216
sessionStorage.removeItem(getFormStorageKey(formId, 'step'));
209217
}
210218

211-
if (config.confirmationType === 'redirect' && config.redirect?.url) {
219+
if (
220+
config.confirmationType === 'redirect' &&
221+
typeof config.redirect?.url === 'string' &&
222+
config.redirect.url.length > 0
223+
) {
212224
router.push(config.redirect.url);
213225
}
214-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
215-
} catch (error: any) {
226+
} catch (error: unknown) {
216227
setStatus('error');
217-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
218-
const message = (error.message as string) || 'Submission failed';
228+
const message =
229+
error instanceof Error && error.message.length > 0 ? error.message : 'Submission failed';
230+
231+
const isFieldValidationError = Object.values(correctHighlightedErrorsText).includes(message);
219232

220-
if (message.includes('initializing Payload')) {
233+
if (isFieldValidationError) {
234+
// Validation errors are already captured prior to throwing this error
235+
setErrorMessage(message);
236+
} else if (message.includes('initializing Payload')) {
221237
posthog.capture('form_submission_error', {
222238
error_type: 'payload_initialization',
223239
form_id: formId,
224240
original_message: message,
225241
});
226242
setErrorMessage(failedToSubmitText[locale]);
227243
} else {
244+
posthog.capture('form_submission_error', {
245+
error_type: 'submission_failed',
246+
form_id: formId,
247+
error_message: message,
248+
});
228249
setErrorMessage(message);
229250
}
230251
}

src/features/payload-cms/components/form/index.tsx

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@ import type { Locale } from '@/types/types'; // Import Locale
44
import { i18nConfig } from '@/types/types';
55
import { cn } from '@/utils/tailwindcss-override';
66
import { useCurrentLocale } from 'next-i18n-router/client';
7+
import { usePostHog } from 'posthog-js/react';
78
import React, { useEffect, useMemo } from 'react';
8-
import { FormProvider, useForm, useWatch, type FieldValues } from 'react-hook-form';
9+
import {
10+
FormProvider,
11+
useForm,
12+
useWatch,
13+
type FieldErrors,
14+
type FieldValues,
15+
} from 'react-hook-form';
916

1017
// Custom Hooks & Components
1118
import { buildEmptyFormState } from '@/features/payload-cms/components/form/build-initial-form-state';
@@ -20,11 +27,21 @@ import type { ConditionedBlock, FormBlockType } from '@/features/payload-cms/com
2027
import { getFormStorageKey } from '@/features/payload-cms/components/form/utils/get-form-storage-key';
2128
export type { FormBlockType } from '@/features/payload-cms/components/form/types';
2229

30+
const formatFieldErrors = (errors: FieldErrors): string => {
31+
return Object.entries(errors)
32+
.map(([field, error]) => {
33+
const message = error?.message as string | undefined;
34+
return typeof message === 'string' && message.length > 0 ? `${field}: ${message}` : field;
35+
})
36+
.join(', ');
37+
};
38+
2339
export const FormBlock: React.FC<
2440
FormBlockType & { isPreviewMode?: boolean; withBorder?: boolean }
2541
> = ({ form: config, isPreviewMode, withBorder = true }) => {
2642
const currentLocale = useCurrentLocale(i18nConfig);
2743
const locale = (currentLocale ?? 'en') as Locale;
44+
const posthog = usePostHog();
2845

2946
// 1. Initialize Form
3047
const formMethods = useForm<FieldValues>({
@@ -142,12 +159,32 @@ export const FormBlock: React.FC<
142159
// Layout-based styles
143160
const isDualCardLayout = isSplit && shouldRenderMain;
144161

162+
const onInvalid = (errors: FieldErrors): void => {
163+
posthog.capture('form_validation_failed', {
164+
form_id: config.id,
165+
error_message: formatFieldErrors(errors),
166+
source: 'client_final_step',
167+
});
168+
};
169+
170+
const handleNext = async (): Promise<void> => {
171+
const isValid = await next();
172+
if (!isValid) {
173+
posthog.capture('form_validation_failed', {
174+
form_id: config.id,
175+
error_message: formatFieldErrors(formMethods.formState.errors),
176+
source: 'client_step_transition',
177+
step: currentStepIndex,
178+
});
179+
}
180+
};
181+
145182
const handleSubmit = (event: React.FormEvent): void => {
146183
event.preventDefault();
147184
if (isLastStep) {
148-
void formMethods.handleSubmit(submit)(event);
185+
void formMethods.handleSubmit(submit, onInvalid)(event);
149186
} else {
150-
void next();
187+
void handleNext();
151188
}
152189
};
153190

@@ -225,7 +262,7 @@ export const FormBlock: React.FC<
225262
isLast={isLastStep}
226263
isSubmitting={status === 'loading'}
227264
// eslint-disable-next-line @typescript-eslint/no-misused-promises
228-
onNext={next}
265+
onNext={handleNext}
229266
onPrev={prev}
230267
submitLabel={config.submitButtonLabel ?? ''}
231268
formId={config.id}
@@ -257,7 +294,7 @@ export const FormBlock: React.FC<
257294
isLast={isLastStep}
258295
isSubmitting={status === 'loading'}
259296
// eslint-disable-next-line @typescript-eslint/no-misused-promises
260-
onNext={next}
297+
onNext={handleNext}
261298
onPrev={prev}
262299
submitLabel={config.submitButtonLabel ?? ''}
263300
formId={config.id}

src/features/payload-cms/payload-cms/collections/timeline/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AdminPanelDashboardGroups } from '@/features/payload-cms/payload-cms/admin-panel-dashboard-groups';
22
import { instagramEmbedBlock } from '@/features/payload-cms/payload-cms/shared-blocks/instagram-embed-block';
3+
import { newsCardBlock } from '@/features/payload-cms/payload-cms/shared-blocks/news-card-block';
34
import { richTextArticleBlock } from '@/features/payload-cms/payload-cms/shared-blocks/rich-text-article-block';
45
import { singlePictureBlock } from '@/features/payload-cms/payload-cms/shared-blocks/single-picture-block';
56
import { internalAuthorsField } from '@/features/payload-cms/payload-cms/shared-fields/internal-authors-field';
@@ -139,7 +140,7 @@ export const TimelineCollection: CollectionConfig = asLocalizedCollection({
139140
fr: 'Le contenu principal de la page',
140141
},
141142
},
142-
blocks: [richTextArticleBlock, singlePictureBlock, instagramEmbedBlock],
143+
blocks: [richTextArticleBlock, singlePictureBlock, instagramEmbedBlock, newsCardBlock],
143144
},
144145
{
145146
name: 'categories',

src/features/payload-cms/payload-cms/tasks/check-hitobito-approvals/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ export const checkHitobitoApprovalsTask: TaskConfig<'checkHitobitoApprovals'> =
1414
});
1515
}
1616
} catch (error: unknown) {
17+
if (
18+
typeof error === 'object' &&
19+
error !== null &&
20+
'status' in error &&
21+
error.status === 404
22+
) {
23+
// Job was likely already deleted by another instance
24+
return;
25+
}
26+
1727
req.payload.logger.error({
1828
err: error instanceof Error ? error : new Error(String(error)),
1929
msg: `Failed to auto-delete completed checkHitobitoApprovals job: ${String(job.id)}`,

src/features/payload-cms/payload-cms/tasks/fetch-smtp-bounces/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ export const fetchSmtpBouncesTask: TaskConfig<'fetchSmtpBounces'> = {
2828
});
2929
}
3030
} catch (error: unknown) {
31+
if (
32+
typeof error === 'object' &&
33+
error !== null &&
34+
'status' in error &&
35+
error.status === 404
36+
) {
37+
// Job was likely already deleted by another instance
38+
return;
39+
}
40+
3141
req.payload.logger.error({
3242
err: error instanceof Error ? error : new Error(String(error)),
3343
msg: `Failed to auto-delete completed fetchSmtpBounces job: ${String(job.id)}`,

0 commit comments

Comments
 (0)