Skip to content

Commit 1d7e688

Browse files
authored
Version 0.40.0 (#959)
2 parents 4aa0995 + 620bf5c commit 1d7e688

File tree

80 files changed

+2581
-955
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2581
-955
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "conveniat27",
3-
"version": "0.39.0",
3+
"version": "0.40.0",
44
"description": "The official website of conveniat27",
55
"license": "MIT",
66
"author": "Cyrill Püntener v/o JPG (cyrill.puentener@cevi.ch)",

src/app/(frontend)/[locale]/[design]/(payload-pages)/offline/page.tsx

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { OfflineLogo } from '@/components/ui/offline-logo';
12
import { HeadlineH1 } from '@/components/ui/typography/headline-h1';
23
import { ParagraphText } from '@/components/ui/typography/paragraph-text';
34
import type { Locale, StaticTranslationString } from '@/types/types';
@@ -41,23 +42,6 @@ export async function generateMetadata({ params }: Properties): Promise<Metadata
4142
};
4243
}
4344

44-
const OfflineLogo: React.FC = () => (
45-
<svg
46-
className="mx-auto h-24 w-24 text-gray-400"
47-
fill="none"
48-
stroke="currentColor"
49-
viewBox="0 0 24 24"
50-
xmlns="http://www.w3.org/2000/svg"
51-
>
52-
<path
53-
strokeLinecap="round"
54-
strokeLinejoin="round"
55-
strokeWidth={1.5}
56-
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414"
57-
/>
58-
</svg>
59-
);
60-
6145
const OfflinePage: React.FC<Properties> = async ({ params }) => {
6246
const { locale } = await params;
6347

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/components/ui/offline-logo.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type React from 'react';
2+
3+
import { cn } from '@/utils/tailwindcss-override';
4+
5+
export const OfflineLogo: React.FC<{ className?: string }> = ({ className }) => (
6+
<svg
7+
className={cn('mx-auto h-24 w-24 text-gray-400', className)}
8+
fill="none"
9+
stroke="currentColor"
10+
viewBox="0 0 24 24"
11+
xmlns="http://www.w3.org/2000/svg"
12+
>
13+
<path
14+
strokeLinecap="round"
15+
strokeLinejoin="round"
16+
strokeWidth={1.5}
17+
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414"
18+
/>
19+
</svg>
20+
);

src/components/ui/pull-to-refresh.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
'use client';
22

3+
import { OfflineLogo } from '@/components/ui/offline-logo';
34
import { cn } from '@/utils/tailwindcss-override';
45
import { animate, motion, useMotionValue } from 'framer-motion';
56
import { Loader2 } from 'lucide-react';
67
import type React from 'react';
78
import { useCallback, useState } from 'react';
89

10+
import { useOnlineStatus } from '@/hooks/use-online-status';
11+
912
interface PullToRefreshProperties {
1013
onRefresh: () => Promise<void>;
1114
children: React.ReactNode;
@@ -19,6 +22,7 @@ export const PullToRefresh: React.FC<PullToRefreshProperties> = ({
1922
className,
2023
pullThreshold = 80,
2124
}) => {
25+
const isOnline = useOnlineStatus();
2226
const [isRefreshing, setIsRefreshing] = useState(false);
2327
const y = useMotionValue(0);
2428

@@ -31,22 +35,35 @@ export const PullToRefresh: React.FC<PullToRefreshProperties> = ({
3135
// Keep it at a fixed position while refreshing
3236
animate(y, 60, { type: 'spring', stiffness: 300, damping: 30 });
3337

34-
void onRefresh().finally(() => {
35-
setIsRefreshing(false);
36-
animate(y, 0, { type: 'spring', stiffness: 300, damping: 30 });
37-
});
38+
if (isOnline) {
39+
void onRefresh().finally(() => {
40+
setIsRefreshing(false);
41+
animate(y, 0, { type: 'spring', stiffness: 300, damping: 30 });
42+
});
43+
} else {
44+
// we are offline, show the offline logo and
45+
// hide the icon after a second
46+
setTimeout(() => {
47+
setIsRefreshing(false);
48+
animate(y, 0, { type: 'spring', stiffness: 300, damping: 30 });
49+
}, 1000);
50+
}
3851
} else {
3952
animate(y, 0, { type: 'spring', stiffness: 500, damping: 30 });
4053
}
41-
}, [y, pullThreshold, isRefreshing, onRefresh]);
54+
}, [y, pullThreshold, isRefreshing, onRefresh, isOnline]);
4255

4356
return (
4457
<div className={cn('relative', className)}>
4558
{/* Spinner shown only while refreshing */}
4659
{isRefreshing && (
4760
<div className="absolute top-4 left-0 z-20 flex w-full justify-center">
4861
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-white shadow-md">
49-
<Loader2 className="text-conveniat-green h-6 w-6 animate-spin" />
62+
{isOnline ? (
63+
<Loader2 className="text-conveniat-green h-6 w-6 animate-spin" />
64+
) : (
65+
<OfflineLogo className="h-6 w-6 text-gray-400" />
66+
)}
5067
</div>
5168
</div>
5269
)}

src/config/environment-variables.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from 'zod';
33

44
export const environmentVariables = createEnv({
55
/*
6-
* Serverside Environment variables, not available on the client.
6+
* Server-side environment variables, not available on the client.
77
* Will throw if you access these variables on the client.
88
*/
99
server: {
@@ -20,7 +20,6 @@ export const environmentVariables = createEnv({
2020
HITOBITO_BASE_URL: z.string().url(),
2121
HITOBITO_FORWARD_URL: z.string().url(),
2222
API_TOKEN: z.string().default(''),
23-
BROWSER_COOKIE: z.string().default(''),
2423
HELPER_GROUP: z.string().optional(),
2524
EVENT_ID: z.string().optional(),
2625
GROUPS_WITH_API_ACCESS: z.string().transform((value) =>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ async function refreshAccessToken(token: JWT): Promise<JWT> {
221221
email: profile.email,
222222
name: profile.first_name + ' ' + profile.last_name,
223223
nickname: profile.nickname,
224+
firstName: profile.first_name,
225+
lastName: profile.last_name,
224226
};
225227
} else {
226228
console.error('Failed to refetch user profile after token refresh');
@@ -303,6 +305,8 @@ export const authOptions: NextAuthConfig = {
303305
cevi_db_uuid: token.cevi_db_uuid,
304306
group_ids: token.group_ids,
305307
nickname: token.nickname,
308+
firstName: token.firstName,
309+
lastName: token.lastName,
306310
};
307311
return session;
308312
},
@@ -333,6 +337,8 @@ export const authOptions: NextAuthConfig = {
333337
email: profile.email,
334338
name: profile.first_name + ' ' + profile.last_name,
335339
nickname: profile.nickname,
340+
firstName: profile.first_name,
341+
lastName: profile.last_name,
336342
};
337343
}
338344

src/features/payload-cms/components/form/actions/get-jobs.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,9 @@ export const getJobs = async (
4747
const currentSubmissionsCount = await payload.count({
4848
collection: 'form-submissions',
4949
where: {
50-
or: [
51-
{
52-
'helper-job': {
53-
equals: job.id,
54-
},
55-
},
56-
{
57-
'helper-jobs': {
58-
contains: job.id,
59-
},
60-
},
61-
],
50+
'helper-jobs': {
51+
contains: job.id,
52+
},
6253
},
6354
});
6455
availableQuota = Math.max(0, job.maxQuota - currentSubmissionsCount.totalDocs);

src/features/payload-cms/components/form/cevi-db-login.tsx

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,25 @@ import {
66
loggedInAsText,
77
loginWithCeviDatabaseText,
88
} from '@/features/payload-cms/components/form/static-form-texts';
9+
import { getFormStorageKey } from '@/features/payload-cms/components/form/utils/get-form-storage-key';
910
import type { StaticTranslationString } from '@/types/types';
1011
import { i18nConfig, type Locale } from '@/types/types';
1112
import { cn } from '@/utils/tailwindcss-override';
1213
import { signIn, signOut, useSession } from 'next-auth/react';
1314
import { useCurrentLocale } from 'next-i18n-router/client';
1415
import React, { useEffect } from 'react';
16+
import type { FieldError, FieldErrorsImpl, FieldValues, Merge } from 'react-hook-form';
1517
import { useFormContext } from 'react-hook-form';
1618

1719
interface CeviDatabaseLoginProperties {
1820
name: string;
1921
label?: string;
2022
saveField?: 'name' | 'uuid' | 'email' | 'nickname';
23+
fieldMapping?: { jwtField: string; formField: string }[];
2124
formId?: string;
2225
required?: boolean;
2326
currentStepIndex?: number;
27+
error?: FieldError | Merge<FieldError, FieldErrorsImpl<FieldValues>>;
2428
}
2529

2630
const loginRequiredMessage: StaticTranslationString = {
@@ -34,25 +38,22 @@ export const CeviDatabaseLogin: React.FC<CeviDatabaseLoginProperties> = ({
3438
label,
3539
required,
3640
saveField,
41+
fieldMapping,
3742
formId,
3843
currentStepIndex,
44+
error,
3945
}) => {
40-
const {
41-
register,
42-
setValue,
43-
getValues,
44-
formState: { errors },
45-
} = useFormContext();
46+
const { register, setValue, getValues } = useFormContext();
4647
const { data: session } = useSession();
4748
const currentLocale = useCurrentLocale(i18nConfig);
4849
const locale = (currentLocale ?? 'en') as Locale;
4950

5051
const handleLogin = (): void => {
5152
const values = getValues();
5253
if (typeof formId === 'string' && formId !== '') {
53-
sessionStorage.setItem(`form-state-${formId}`, JSON.stringify(values));
54+
sessionStorage.setItem(getFormStorageKey(formId, 'state'), JSON.stringify(values));
5455
if (currentStepIndex !== undefined) {
55-
sessionStorage.setItem(`form_step_${formId}`, String(currentStepIndex));
56+
sessionStorage.setItem(getFormStorageKey(formId, 'step'), String(currentStepIndex));
5657
}
5758
}
5859
// Inform browser to replace the current history entry so the back button skips the login trigger
@@ -71,9 +72,9 @@ export const CeviDatabaseLogin: React.FC<CeviDatabaseLoginProperties> = ({
7172
const handleChangeUser = (): void => {
7273
const values = getValues();
7374
if (typeof formId === 'string' && formId !== '') {
74-
sessionStorage.setItem(`form-state-${formId}`, JSON.stringify(values));
75+
sessionStorage.setItem(getFormStorageKey(formId, 'state'), JSON.stringify(values));
7576
if (currentStepIndex !== undefined) {
76-
sessionStorage.setItem(`form_step_${formId}`, String(currentStepIndex));
77+
sessionStorage.setItem(getFormStorageKey(formId, 'step'), String(currentStepIndex));
7778
}
7879
}
7980
const callbackUrl = typeof globalThis === 'undefined' ? undefined : globalThis.location.href;
@@ -86,6 +87,8 @@ export const CeviDatabaseLogin: React.FC<CeviDatabaseLoginProperties> = ({
8687
});
8788
};
8889

90+
const fieldMappingString = fieldMapping ? JSON.stringify(fieldMapping) : undefined;
91+
8992
useEffect(() => {
9093
if (session?.user) {
9194
let valueToSave: string | number | undefined | null;
@@ -108,10 +111,78 @@ export const CeviDatabaseLogin: React.FC<CeviDatabaseLoginProperties> = ({
108111
}
109112
}
110113
setValue(name, valueToSave ?? '', { shouldValidate: true });
114+
115+
const parsedFieldMapping = fieldMappingString
116+
? (JSON.parse(fieldMappingString) as { jwtField: string; formField: string }[])
117+
: undefined;
118+
119+
if (parsedFieldMapping && parsedFieldMapping.length > 0) {
120+
let didPrefill = false;
121+
for (const { jwtField, formField } of parsedFieldMapping) {
122+
let jwtValue: string | number | null | undefined;
123+
switch (jwtField) {
124+
case 'name': {
125+
jwtValue = session.user.name;
126+
break;
127+
}
128+
case 'firstName': {
129+
jwtValue = session.user.firstName;
130+
break;
131+
}
132+
case 'lastName': {
133+
jwtValue = session.user.lastName;
134+
break;
135+
}
136+
case 'email': {
137+
jwtValue = session.user.email;
138+
break;
139+
}
140+
case 'nickname': {
141+
jwtValue = session.user.nickname;
142+
break;
143+
}
144+
case 'uuid': {
145+
jwtValue = session.user.uuid;
146+
break;
147+
}
148+
case 'cevi_db_uuid': {
149+
jwtValue = session.user.cevi_db_uuid;
150+
break;
151+
}
152+
default: {
153+
break;
154+
}
155+
}
156+
const jwtValueString =
157+
jwtValue !== undefined && jwtValue !== null ? String(jwtValue).trim() : '';
158+
159+
if (jwtValueString.length > 0) {
160+
const currentValue = getValues(formField) as unknown;
161+
let currentValueString = '';
162+
if (typeof currentValue === 'string' || typeof currentValue === 'number') {
163+
currentValueString = String(currentValue).trim();
164+
} else if (currentValue !== undefined && currentValue !== null) {
165+
currentValueString = 'has_value';
166+
}
167+
168+
if (currentValueString.length === 0) {
169+
setValue(formField, String(jwtValue), { shouldValidate: true });
170+
didPrefill = true;
171+
}
172+
}
173+
}
174+
175+
if (didPrefill && typeof formId === 'string' && currentStepIndex !== undefined) {
176+
sessionStorage.setItem(
177+
getFormStorageKey(formId, 'prefill'),
178+
String(currentStepIndex + 1),
179+
);
180+
}
181+
}
111182
}
112-
}, [session, saveField, setValue, name]);
183+
}, [session, saveField, setValue, name, fieldMappingString, getValues, formId, currentStepIndex]);
113184

114-
const errorMessage = errors[name]?.message as string | undefined;
185+
const errorMessage = error ? (error as FieldError).message : undefined;
115186

116187
const nickname = session?.user.nickname;
117188
const hasNickname = typeof nickname === 'string' && nickname.length > 0;

0 commit comments

Comments
 (0)