diff --git a/packages/api/src/fetchUserGroups.ts b/packages/api/src/fetchUserGroups.ts deleted file mode 100644 index 9b89554e..00000000 --- a/packages/api/src/fetchUserGroups.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GetGroupsResponse } from './types/GroupType'; - -async function fetchUserGroups(userId: string): Promise { - try { - const response = await fetch(`${import.meta.env.VITE_SERVER_URL}/api/users/${userId}/groups`, { - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!response.ok) { - throw new Error(`HTTP Error! Status: ${response.status}`); - } - - const userGroups = (await response.json()) as { data: GetGroupsResponse[] }; - return userGroups.data; - } catch (error) { - console.error('Error fetching user groups:', error); - return null; - } -} - -export default fetchUserGroups; diff --git a/packages/api/src/fetchUsersToGroups.ts b/packages/api/src/fetchUsersToGroups.ts new file mode 100644 index 00000000..533361c0 --- /dev/null +++ b/packages/api/src/fetchUsersToGroups.ts @@ -0,0 +1,27 @@ +import { GetUsersToGroupsResponse } from './types'; + +async function fetchUsersToGroups(userId: string): Promise { + try { + const response = await fetch( + `${import.meta.env.VITE_SERVER_URL}/api/users/${userId}/users-to-groups`, + { + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + if (!response.ok) { + throw new Error(`HTTP Error! Status: ${response.status}`); + } + + const userGroups = (await response.json()) as { data: GetUsersToGroupsResponse }; + return userGroups.data; + } catch (error) { + console.error('Error fetching user groups:', error); + return null; + } +} + +export default fetchUsersToGroups; diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 8c0adb66..53717773 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -17,7 +17,7 @@ export { default as fetchRegistrationFields } from './fetchRegistrationFields'; export { default as fetchRegistrations } from './fetchRegistrations'; export { default as fetchUserAttributes } from './fetchUserAttributes'; export { default as fetchUser } from './fetchUser'; -export { default as fetchUserGroups } from './fetchUserGroups'; +export { default as fetchUsersToGroups } from './fetchUsersToGroups'; export { default as fetchUserRegistrations } from './fetchUserRegistrations'; export { default as fetchUserVotes } from './fetchUserVotes'; export { default as logout } from './logout'; @@ -26,8 +26,9 @@ export { default as postGroup } from './postGroup'; export { default as postLike } from './postLike'; export { default as postPcdStr } from './postVerify'; export { default as postRegistration } from './postRegistration'; -export { default as postUserToGroups } from './postUserToGroups'; +export { default as postUsersToGroups } from './postUsersToGroups'; export { default as postVotes } from './postVotes'; export { default as putRegistration } from './putRegistration'; -export { default as updateUser } from './putUser'; +export { default as putUser } from './putUser'; +export { default as putUsersToGroups } from './putUsersToGroups'; export * from './types'; diff --git a/packages/api/src/postUserToGroups.ts b/packages/api/src/postUsersToGroups.ts similarity index 100% rename from packages/api/src/postUserToGroups.ts rename to packages/api/src/postUsersToGroups.ts diff --git a/packages/api/src/putUsersToGroups.ts b/packages/api/src/putUsersToGroups.ts index 6e817e6e..024a5a20 100644 --- a/packages/api/src/putUsersToGroups.ts +++ b/packages/api/src/putUsersToGroups.ts @@ -1,6 +1,6 @@ import { PostUsersToGroupsResponse, PutUsersToGroupsRequest } from './types'; -async function postUserToGroups({ +async function postUsersToGroups({ groupId, userToGroupId, }: PutUsersToGroupsRequest): Promise { @@ -8,7 +8,7 @@ async function postUserToGroups({ const response = await fetch( `${import.meta.env.VITE_SERVER_URL}/api/users-to-groups/${userToGroupId}`, { - method: 'POST', + method: 'PUT', credentials: 'include', headers: { 'Content-Type': 'application/json', @@ -32,4 +32,4 @@ async function postUserToGroups({ } } -export default postUserToGroups; +export default postUsersToGroups; diff --git a/packages/api/src/types/UserToGroupsType.ts b/packages/api/src/types/UserToGroupsType.ts deleted file mode 100644 index 014f02a3..00000000 --- a/packages/api/src/types/UserToGroupsType.ts +++ /dev/null @@ -1,21 +0,0 @@ -type UsersToGroups = { - id: string; - createdAt: Date; - updatedAt: Date; - userId: string; - groupCategoryId: string | null; - groupId: string; -}; - -export type PostUsersToGroupsRequest = { - groupId?: string; - secret?: string; -}; - -export type PutUsersToGroupsRequest = { - userToGroupId: string; - groupId: string; -}; - -export type PostUsersToGroupsResponse = UsersToGroups; -export type PutUsersToGroupsResponse = UsersToGroups; diff --git a/packages/api/src/types/UsersToGroupsType.ts b/packages/api/src/types/UsersToGroupsType.ts new file mode 100644 index 00000000..1fb11d98 --- /dev/null +++ b/packages/api/src/types/UsersToGroupsType.ts @@ -0,0 +1,46 @@ +type UsersToGroups = { + id: string; + createdAt: Date; + updatedAt: Date; + userId: string; + groupCategoryId: string | null; + groupId: string; +}; + +export type GetUsersToGroupsResponse = { + id: string; + createdAt: string; + updatedAt: string; + userId: string; + groupCategoryId: string | null; + groupId: string; + group: { + id: string; + name: string; + createdAt: Date; + updatedAt: Date; + description: string | null; + secret: string | null; + groupCategoryId: string | null; + groupCategory?: { + createdAt: string; + eventId: string; + id: string; + name: string; + updatedAt: string; + }; + }; +}[]; + +export type PostUsersToGroupsRequest = { + groupId?: string; + secret?: string; +}; + +export type PutUsersToGroupsRequest = { + userToGroupId: string; + groupId: string; +}; + +export type PostUsersToGroupsResponse = UsersToGroups; +export type PutUsersToGroupsResponse = UsersToGroups; diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index c46edcfb..9d8f4a16 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -11,6 +11,6 @@ export * from './RegistrationFieldOptionType'; export * from './RegistrationFieldType'; export * from './RegistrationType'; export * from './UserAttributesType'; -export * from './UserToGroupsType'; +export * from './UsersToGroupsType'; export * from './UserType'; export * from './UserVotesType'; diff --git a/packages/berlin/src/App.tsx b/packages/berlin/src/App.tsx index 26f718a8..0cad8651 100644 --- a/packages/berlin/src/App.tsx +++ b/packages/berlin/src/App.tsx @@ -6,7 +6,7 @@ import { QueryClient } from '@tanstack/react-query'; import { useAppStore } from './store'; // API -import { fetchEvents, fetchUserData, fetchCycle, fetchRegistrations } from 'api'; +import { fetchEvents, fetchUser, fetchCycle, fetchRegistrations } from 'api'; // Pages import { default as BerlinLayout } from './layout/index.ts'; @@ -31,7 +31,7 @@ import SecretGroupRegistration from './pages/SecretGroupRegistration.tsx'; async function redirectToLandingLoader(queryClient: QueryClient) { const user = await queryClient.fetchQuery({ queryKey: ['user'], - queryFn: fetchUserData, + queryFn: fetchUser, staleTime: 10000, }); @@ -47,7 +47,7 @@ async function redirectToLandingLoader(queryClient: QueryClient) { async function redirectToAccount(queryClient: QueryClient) { const user = await queryClient.fetchQuery({ queryKey: ['user'], - queryFn: fetchUserData, + queryFn: fetchUser, }); if (user?.username) { @@ -65,7 +65,7 @@ async function redirectToAccount(queryClient: QueryClient) { async function redirectOnLandingLoader(queryClient: QueryClient) { const user = await queryClient.fetchQuery({ queryKey: ['user'], - queryFn: fetchUserData, + queryFn: fetchUser, }); if (!user) { diff --git a/packages/berlin/src/hooks/useUser.tsx b/packages/berlin/src/hooks/useUser.tsx index 4c42d690..28f17cef 100644 --- a/packages/berlin/src/hooks/useUser.tsx +++ b/packages/berlin/src/hooks/useUser.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { fetchUserData } from 'api'; +import { fetchUser } from 'api'; function useUser() { const { @@ -8,7 +8,7 @@ function useUser() { isError, } = useQuery({ queryKey: ['user'], - queryFn: fetchUserData, + queryFn: fetchUser, }); return { user, isLoading, isError }; diff --git a/packages/berlin/src/pages/Account.tsx b/packages/berlin/src/pages/Account.tsx index 7f9e66c4..0c854c16 100644 --- a/packages/berlin/src/pages/Account.tsx +++ b/packages/berlin/src/pages/Account.tsx @@ -6,12 +6,14 @@ import toast from 'react-hot-toast'; // API Calls import { - GetUserResponse, + type GetUserResponse, fetchEvents, fetchGroups, fetchUserAttributes, - fetchUserGroups, - updateUserData, + fetchUsersToGroups, + putUser, + putUsersToGroups, + postUsersToGroups, } from 'api'; // Components @@ -59,7 +61,8 @@ type InitialUser = { firstName: string; lastName: string; email: string; - group: string; + userToGroupId?: string; + groupId: string; telegram: string | null; userAttributes: UserAttributes | undefined; }; @@ -73,9 +76,9 @@ function Account() { enabled: !!user?.id, }); - const { data: userGroups, isLoading: userGroupsIsLoading } = useQuery({ + const { data: usersToGroups, isLoading: userGroupsIsLoading } = useQuery({ queryKey: ['user', user?.id, 'groups'], - queryFn: () => fetchUserGroups(user?.id || ''), + queryFn: () => fetchUsersToGroups(user?.id || ''), enabled: !!user?.id, }); @@ -98,7 +101,9 @@ function Account() { lastName: user?.lastName || '', email: user?.email || '', telegram: user?.telegram || null, - group: (userGroups && userGroups[0]?.id) || '', + userToGroupId: usersToGroups?.find((g) => g.group.groupCategory?.name === 'affiliation')?.id, + groupId: + usersToGroups?.find((g) => g.group.groupCategory?.name === 'affiliation')?.group.id || '', userAttributes: userAttributes?.reduce( (acc, curr) => { if (curr.attributeKey === 'credentialsGroup') { @@ -136,7 +141,7 @@ function Account() { } as UserAttributes, ), }; - }, [user, userGroups, userAttributes]); + }, [user, usersToGroups, userAttributes]); if (userIsLoading || userGroupsIsLoading || userAttributesIsLoading) { return Loading...; @@ -161,7 +166,7 @@ function AccountForm({ const queryClient = useQueryClient(); const { mutate: mutateUserData } = useMutation({ - mutationFn: updateUserData, + mutationFn: putUser, onSuccess: async (body) => { console.log({ body }); if (!body) { @@ -231,22 +236,21 @@ function AccountForm({ control, }); - const watchedGroupInputId = useWatch({ control, name: 'group' }); + const watchedGroupInputId = useWatch({ control, name: 'groupId' }); const customGroupName = 'Custom Affiliation'; const customGroup = groups?.find( (group) => group.name.toLocaleLowerCase() === customGroupName.toLocaleLowerCase(), ); - const onSubmit = (value: typeof initialUser) => { + const onSubmit = async (value: typeof initialUser) => { if (isValid && user && user.id) { - mutateUserData({ + await mutateUserData({ userId: user.id, username: value.username, email: value.email, firstName: value.firstName, lastName: value.lastName, telegram: value.telegram?.trim() !== '' ? value.telegram?.trim() : null, - groupIds: [value.group], userAttributes: { ...value.userAttributes, credentialsGroup: JSON.stringify(value.userAttributes?.credentialsGroup), @@ -254,6 +258,18 @@ function AccountForm({ contributions: JSON.stringify(value.userAttributes?.contributions), }, }); + + // Create user to group if it doesn't exist + if (!value.userToGroupId) { + await postUsersToGroups({ + groupId: value.groupId, + }); + } else { + await putUsersToGroups({ + groupId: value.groupId, + userToGroupId: value.userToGroupId, + }); + } } }; @@ -278,8 +294,8 @@ function AccountForm({ if (customGroup) { // set group to custom group id - setValue('group', customGroup?.id); - trigger('group'); + setValue('groupId', customGroup?.id); + trigger('groupId'); } // set otherGroupName to value setValue('userAttributes.customGroupName', value); @@ -323,7 +339,7 @@ function AccountForm({ {...register('telegram')} /> ( @@ -340,7 +356,7 @@ function AccountForm({ onBlur={field.onBlur} onOptionCreate={(value) => groupOnCreate(groups, customGroupName, value)} value={field.value} - errors={[errors.group?.message ?? '']} + errors={[errors.groupId?.message ?? '']} /> {watchedGroupInputId === customGroup?.id && initialUser.userAttributes?.customGroupName && ( diff --git a/packages/berlin/src/pages/PublicGroupRegistration.tsx b/packages/berlin/src/pages/PublicGroupRegistration.tsx index 40416290..bd1ee3f3 100644 --- a/packages/berlin/src/pages/PublicGroupRegistration.tsx +++ b/packages/berlin/src/pages/PublicGroupRegistration.tsx @@ -8,7 +8,7 @@ import toast from 'react-hot-toast'; import useUser from '../hooks/useUser'; // API -import { fetchGroups, postUserToGroups } from 'api'; +import { fetchGroups, postUsersToGroups } from 'api'; // Data import publicGroups from '../data/publicGroups'; @@ -50,8 +50,8 @@ function PublicGroupRegistration() { const selectData = groups?.map((group) => ({ id: group.id, name: group.name })) ?? []; - const { mutate: postUserToGroupsMutation } = useMutation({ - mutationFn: postUserToGroups, + const { mutate: postUsersToGroupsMutation } = useMutation({ + mutationFn: postUsersToGroups, onSuccess: (body) => { if (!body) { return; @@ -66,7 +66,7 @@ function PublicGroupRegistration() { const onSubmit = () => { if (isValid) { - postUserToGroupsMutation({ groupId: getValues('group') }); + postUsersToGroupsMutation({ groupId: getValues('group') }); setValue('group', ''); reset(); } diff --git a/packages/berlin/src/pages/Register.tsx b/packages/berlin/src/pages/Register.tsx index d4e9dbdf..26e7b839 100644 --- a/packages/berlin/src/pages/Register.tsx +++ b/packages/berlin/src/pages/Register.tsx @@ -13,7 +13,7 @@ import { fetchRegistrationData, fetchRegistrationFields, fetchRegistrations, - fetchUserGroups, + fetchUsersToGroups, GetRegistrationsResponseType, postRegistration, putRegistration, @@ -72,15 +72,16 @@ function Register() { }); // this query runs if there is a groupCategory query param. - const { data: userGroups } = useQuery({ + const { data: usersToGroups } = useQuery({ queryKey: ['user', 'groups', user?.id], - queryFn: () => fetchUserGroups(user?.id || ''), + queryFn: () => fetchUsersToGroups(user?.id || ''), enabled: !!user?.id && !!groupCategoryParam, }); const groupId = useMemo(() => { - return userGroups?.find((group) => group.groupCategory?.name === groupCategoryParam)?.id; - }, [groupCategoryParam, userGroups]); + return usersToGroups?.find((group) => group.group.groupCategory?.name === groupCategoryParam) + ?.id; + }, [groupCategoryParam, usersToGroups]); useEffect(() => { // select the first registration if it exists diff --git a/packages/berlin/src/pages/SecretGroupRegistration.tsx b/packages/berlin/src/pages/SecretGroupRegistration.tsx index bb98e76c..e5b74dc5 100644 --- a/packages/berlin/src/pages/SecretGroupRegistration.tsx +++ b/packages/berlin/src/pages/SecretGroupRegistration.tsx @@ -8,7 +8,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import toast from 'react-hot-toast'; // API -import { postUserToGroups, fetchGroupCategories, postGroup } from 'api'; +import { postUsersToGroups, fetchGroupCategories, postGroup } from 'api'; // Data import groups from '../data/groups'; @@ -70,8 +70,8 @@ function SecretGroupRegistration() { }, }); - const { mutate: postUserToGroupsMutation } = useMutation({ - mutationFn: postUserToGroups, + const { mutate: postUsersToGroupsMutation } = useMutation({ + mutationFn: postUsersToGroups, onSuccess: (body) => { if (!body) { return; @@ -93,7 +93,7 @@ function SecretGroupRegistration() { const onSubmit = () => { if (isValid) { - postUserToGroupsMutation({ secret: getValues('secret') }); + postUsersToGroupsMutation({ secret: getValues('secret') }); reset(); } }; diff --git a/packages/core/src/hooks/useUser.tsx b/packages/core/src/hooks/useUser.tsx index 5fc130d7..42d81329 100644 --- a/packages/core/src/hooks/useUser.tsx +++ b/packages/core/src/hooks/useUser.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { fetchUserData } from 'api/'; +import { fetchUser } from 'api/'; function useUser() { const { @@ -8,7 +8,7 @@ function useUser() { isError, } = useQuery({ queryKey: ['user'], - queryFn: fetchUserData, + queryFn: fetchUser, retry: false, staleTime: 10000, }); diff --git a/packages/core/src/pages/Account.tsx b/packages/core/src/pages/Account.tsx index 09ff28bc..6196e7eb 100644 --- a/packages/core/src/pages/Account.tsx +++ b/packages/core/src/pages/Account.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { fetchEvents, fetchGroups, fetchUserGroups, updateUserData } from 'api'; +import { fetchEvents, fetchGroups, fetchUsersToGroups, putUser } from 'api'; import { Controller, useForm } from 'react-hook-form'; import toast from 'react-hot-toast'; import { useNavigate } from 'react-router-dom'; @@ -36,19 +36,22 @@ function Account() { const { data: groups } = useQuery({ queryKey: ['groups'], - queryFn: fetchGroups, + queryFn: () => + fetchGroups({ + groupCategoryName: 'affiliation', + }), enabled: !!user?.id, }); const { data: userGroups, isLoading: userGroupsIsLoading } = useQuery({ queryKey: ['user', user?.id, 'groups'], - queryFn: () => fetchUserGroups(user?.id || ''), + queryFn: () => fetchUsersToGroups(user?.id || ''), enabled: !!user?.id, }); const initialUser = { username: user?.username || '', - name: user?.name || '', + name: user?.firstName || '', email: user?.email || '', group: (userGroups && userGroups[0]?.id) || '', }; @@ -62,7 +65,7 @@ function Account() { initialUser={initialUser} user={user} groups={groups} - userGroups={userGroups} + userGroups={[]} events={events} /> ); @@ -87,7 +90,7 @@ function AccountForm({ const setUserStatus = useAppStore((state) => state.setUserStatus); const { mutate: mutateUserData } = useMutation({ - mutationFn: updateUserData, + mutationFn: putUser, onSuccess: (body) => { if (body) { queryClient.invalidateQueries({ queryKey: ['user'] }); @@ -115,7 +118,8 @@ function AccountForm({ userId: user.id, username: value.username, email: value.email, - groupIds: [value.group], + firstName: '', + lastName: '', userAttributes: { name: value.name, },