-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathPartiesProvider.tsx
223 lines (188 loc) · 7.91 KB
/
PartiesProvider.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router';
import type { PropsWithChildren } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useAppMutations, useAppQueries } from 'src/core/contexts/AppQueriesProvider';
import { createContext } from 'src/core/contexts/context';
import { delayedContext } from 'src/core/contexts/delayedContext';
import { createQueryContext } from 'src/core/contexts/queryContext';
import { DisplayError } from 'src/core/errorHandling/DisplayError';
import { Loader } from 'src/core/loading/Loader';
import { instanceQueryKeys } from 'src/features/instance/InstanceContext';
import { NoValidPartiesError } from 'src/features/instantiate/containers/NoValidPartiesError';
import { useShouldFetchProfile } from 'src/features/profile/ProfileProvider';
import type { IInstance, IParty } from 'src/types/shared';
import type { HttpClientError } from 'src/utils/network/sharedNetworking';
const partyQueryKeys = {
all: ['parties'] as const,
allowedToInstantiate: () => [...partyQueryKeys.all, 'allowedToInstantiate'] as const,
};
// Also used for prefetching @see appPrefetcher.ts, partyPrefetcher.ts
export function usePartiesQueryDef(enabled: boolean) {
const { fetchPartiesAllowedToInstantiate } = useAppQueries();
return {
queryKey: partyQueryKeys.allowedToInstantiate(),
queryFn: fetchPartiesAllowedToInstantiate,
enabled,
};
}
const usePartiesAllowedToInstantiateQuery = () => {
const enabled = useShouldFetchProfile();
const utils = useQuery(usePartiesQueryDef(enabled));
useEffect(() => {
utils.error && window.logError('Fetching parties failed:\n', utils.error);
}, [utils.error]);
return {
...utils,
enabled,
};
};
// Also used for prefetching @see appPrefetcher.ts, partyPrefetcher.ts
export function useCurrentPartyQueryDef(enabled: boolean) {
const { fetchCurrentParty } = useAppQueries();
return {
queryKey: ['fetchUseCurrentParty', enabled],
queryFn: fetchCurrentParty,
enabled,
};
}
const useCurrentPartyQuery = (enabled: boolean) => {
const query = useQuery(useCurrentPartyQueryDef(enabled));
useEffect(() => {
query.error && window.logError('Fetching current party failed:\n', query.error);
}, [query.error]);
return query;
};
const useSetCurrentPartyMutation = () => {
const { doSetCurrentParty } = useAppMutations();
return useMutation({
mutationKey: ['doSetCurrentParty'],
mutationFn: (party: IParty) => doSetCurrentParty(party.partyId),
onError: (error: HttpClientError) => {
window.logError('Setting current party failed:\n', error);
},
});
};
const { Provider: PartiesProvider, useCtx: usePartiesAllowedToInstantiateCtx } = delayedContext(() =>
createQueryContext<IParty[] | undefined, false>({
name: 'Parties',
required: false,
default: undefined,
query: usePartiesAllowedToInstantiateQuery,
}),
);
interface CurrentParty {
party: IParty | undefined;
currentIsValid: boolean | undefined;
userHasSelectedParty: boolean | undefined;
setUserHasSelectedParty: (hasSelected: boolean) => void;
setParty: (party: IParty) => Promise<IParty | undefined>;
}
const { Provider: RealCurrentPartyProvider, useCtx: useCurrentPartyCtx } = createContext<CurrentParty>({
name: 'CurrentParty',
required: false,
default: {
party: undefined,
currentIsValid: undefined,
userHasSelectedParty: undefined,
setUserHasSelectedParty: () => {
throw new Error('CurrentPartyProvider not initialized');
},
setParty: () => {
throw new Error('CurrentPartyProvider not initialized');
},
},
});
/*
* This provider is used to manage the selected party and its validity _before_ any instance is present.
* That is, the selected party should only be used to determine the party that is used to instantiate an app or to select from previously instantiated apps.
* When the user is filling out an app, the current party is always the user's party, found in the profile, filling out the form on behalf of the instance owner.
*/
const CurrentPartyProvider = ({ children }: PropsWithChildren) => {
const validParties = useValidParties();
const [sentToMutation, setSentToMutation] = useState<IParty | undefined>(undefined);
const { mutateAsync, data: dataFromMutation, error: errorFromMutation } = useSetCurrentPartyMutation();
const { data: partyFromQuery, isLoading, error: errorFromQuery } = useCurrentPartyQuery(true);
const [userHasSelectedParty, setUserHasSelectedParty] = useState(false);
if (isLoading) {
return <Loader reason='current-party' />;
}
const error = errorFromMutation || errorFromQuery;
if (error) {
return <DisplayError error={error} />;
}
if (!validParties?.length) {
return <NoValidPartiesError />;
}
const partyFromMutation = dataFromMutation === 'Party successfully updated' ? sentToMutation : undefined;
const currentParty = partyFromMutation ?? partyFromQuery;
const currentIsValid = currentParty && validParties?.some((party) => party.partyId === currentParty.partyId);
return (
<RealCurrentPartyProvider
value={{
party: currentParty,
currentIsValid,
userHasSelectedParty,
setUserHasSelectedParty: (hasSelected: boolean) => setUserHasSelectedParty(hasSelected),
setParty: async (party) => {
try {
setSentToMutation(party);
const result = await mutateAsync(party);
if (result === 'Party successfully updated') {
return party;
}
return undefined;
} catch (_err) {
// Ignoring error here, as it's handled by this provider
}
},
}}
>
{children}
</RealCurrentPartyProvider>
);
};
export function PartyProvider({ children }: PropsWithChildren) {
const shouldFetchProfile = useShouldFetchProfile();
if (!shouldFetchProfile) {
return children;
}
return (
<PartiesProvider>
<CurrentPartyProvider>{children}</CurrentPartyProvider>
</PartiesProvider>
);
}
export const usePartiesAllowedToInstantiate = () => usePartiesAllowedToInstantiateCtx();
/**
* Returns the current party, or the custom selected current party if one is set.
* Please note that the current party might not be allowed to instantiate, so you should
* check the `canInstantiate` property as well.
*/
export const useCurrentParty = () => useCurrentPartyCtx().party;
export const useCurrentPartyIsValid = () => useCurrentPartyCtx().currentIsValid;
export const useSetCurrentParty = () => useCurrentPartyCtx().setParty;
export const useValidParties = () => usePartiesAllowedToInstantiateCtx()?.filter((party) => party.isDeleted === false);
export const useHasSelectedParty = () => useCurrentPartyCtx().userHasSelectedParty;
export const useSetHasSelectedParty = () => useCurrentPartyCtx().setUserHasSelectedParty;
export function useInstanceOwnerParty(): IParty | null {
const parties = usePartiesAllowedToInstantiate() ?? [];
const queryClient = useQueryClient();
const { instanceOwnerPartyId, instanceGuid } = useParams();
const instanceOwner = queryClient.getQueryData<IInstance>(
instanceQueryKeys.instanceData(instanceOwnerPartyId, instanceGuid),
)?.instanceOwner;
if (!instanceOwner) {
return null;
}
// If the backend is updated to v8.6.0 it will return the whole party object on the instance owner,
// so we can use that directly.
if (instanceOwner?.party) {
return instanceOwner.party;
}
// Backwards compatibility: if the backend returns only the partyId, we need to find the party in the list of parties.
// This logic assumes that the current logged in user has "access" to the party of the instance owner,
// as the parties array comes from the current users party list.
const flattenedParties = [...parties, ...parties.flatMap((party) => party.childParties ?? [])];
return flattenedParties?.find((party) => party.partyId.toString() === instanceOwner.partyId) ?? null;
}