Remove payments API routing initialization
[ProtonMail-WebClient.git] / packages / components / containers / vpn / gateways / GatewayModal.tsx
blobd3a28aa432709845be43b188803a8a69b8eb76e6
1 import { useMemo, useState } from 'react';
3 import { c } from 'ttag';
5 import { Button } from '@proton/atoms';
6 import { useApiResult, useModalTwoStatic } from '@proton/components';
7 import Form from '@proton/components/components/form/Form';
8 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
9 import ModalTwo from '@proton/components/components/modalTwo/Modal';
10 import ModalTwoContent from '@proton/components/components/modalTwo/ModalContent';
11 import ModalTwoFooter from '@proton/components/components/modalTwo/ModalFooter';
12 import ModalTwoHeader from '@proton/components/components/modalTwo/ModalHeader';
13 import useFormErrors from '@proton/components/components/v2/useFormErrors';
14 import { Loader } from '@proton/components/index';
16 import type { CountryOptions } from '../../../helpers/countries';
17 import AddServerConfirmationModal from './AddServerConfirmationModal';
18 import type { DeletedDedicatedIp } from './DeletedDedicatedIp';
19 import { GatewayCountrySelection } from './GatewayCountrySelection';
20 import type { GatewayDto } from './GatewayDto';
21 import type { GatewayLocation } from './GatewayLocation';
22 import type { GatewayModel } from './GatewayModel';
23 import { GatewayNameField } from './GatewayNameField';
24 import type { GatewayUser } from './GatewayUser';
25 import { GatewayUserSelection } from './GatewayUserSelection';
26 import { queryDeletedDedicatedIPs } from './api';
27 import { getInitialModel } from './helpers';
28 import { useAddedQuantities, useUnassigningAddedQuantities } from './useAddedQuantities';
29 import { useSpecificCountryCount } from './useSpecificCountryCount';
31 interface Props extends ModalProps<typeof Form> {
32     locations: readonly GatewayLocation[];
33     deletedInCountries: Record<string, number>;
34     ownedCount: number;
35     usedCount: number;
36     users: readonly GatewayUser[];
37     countryOptions: CountryOptions;
38     isEditing?: boolean;
39     singleServer?: boolean;
40     showCancelButton?: boolean;
41     onSubmitDone: (server: GatewayModel) => Promise<void>;
44 enum STEP {
45     NAME,
46     COUNTRIES,
47     MEMBERS,
50 const GatewayModal = ({
51     locations,
52     deletedInCountries,
53     ownedCount,
54     usedCount,
55     users,
56     countryOptions,
57     onSubmitDone,
58     isEditing = false,
59     singleServer = false,
60     showCancelButton = false,
61     ...rest
62 }: Props) => {
63     const { loading: deletedDedicatedIPsLoading, result } = useApiResult<
64         { DedicatedIps: DeletedDedicatedIp[] },
65         typeof queryDeletedDedicatedIPs
66     >(queryDeletedDedicatedIPs, []);
68     const deletedDedicatedIPs = result?.DedicatedIps;
70     const [addServerConfirmationModal, showAddServerConfirmationModal] = useModalTwoStatic(AddServerConfirmationModal);
72     const { validator, onFormSubmit } = useFormErrors();
73     const [step, setStep] = useState(STEP.NAME);
74     const [model, setModel] = useState(getInitialModel(locations));
75     const [loading, setLoading] = useState(false);
76     const remainingCount = useMemo(() => ownedCount - usedCount, [ownedCount, usedCount]);
77     const addedCount = useAddedQuantities(model);
78     const totalAddedCount = addedCount + useUnassigningAddedQuantities(model);
79     const totalCountExceeded = useMemo(
80         () => addedCount > remainingCount - (deletedDedicatedIPs?.length || 0),
81         [addedCount, remainingCount]
82     );
83     const specificCountryCount = useSpecificCountryCount(model, remainingCount, deletedInCountries);
84     const needUpsell = useMemo(
85         () => totalCountExceeded || specificCountryCount > 0,
86         [totalCountExceeded, specificCountryCount]
87     );
88     const canContinue = useMemo(
89         () => step !== STEP.COUNTRIES || !(needUpsell || (!singleServer && totalAddedCount < 1)),
90         [step, needUpsell, singleServer, totalAddedCount]
91     );
93     const changeModel = (diff: Partial<GatewayDto>) => setModel((model: GatewayDto) => ({ ...model, ...diff }));
95     const stepBack = () => {
96         if (step === STEP.MEMBERS) {
97             setStep(locations.length > 1 ? STEP.COUNTRIES : STEP.NAME);
99             return;
100         }
102         if (step === STEP.COUNTRIES) {
103             setStep(STEP.NAME);
105             return;
106         }
108         rest.onClose?.();
109     };
111     const handleSubmit = async () => {
112         if (!onFormSubmit()) {
113             return;
114         }
116         if (step === STEP.NAME) {
117             setStep(STEP.COUNTRIES);
119             return;
120         }
122         const quantities: Record<string, number> = {};
123         let total = 0;
125         Object.keys(model.quantities || {}).forEach((locationId) => {
126             const count = model.quantities?.[locationId] || 0;
128             if (count > 0) {
129                 quantities[locationId] = count;
130                 total += count;
131             }
132         });
134         Object.keys(model.unassignedIpQuantities || {}).forEach((locationId) => {
135             const count = model.unassignedIpQuantities?.[locationId] || 0;
137             if (count > 0) {
138                 quantities[locationId] = (quantities[locationId] || 0) + count;
139                 total += count;
140             }
141         });
143         if (step === STEP.COUNTRIES) {
144             showAddServerConfirmationModal({
145                 onSubmitDone: () => {
146                     setStep(STEP.MEMBERS);
147                 },
148                 totalQuantities: quantities,
149                 countryOptions,
150             });
152             return;
153         }
155         const dtoBody: GatewayModel =
156             singleServer || total === 1
157                 ? {
158                       Name: model.name,
159                       Location: model.location,
160                       Features: model.features,
161                       UserIds: model.userIds,
162                   }
163                 : {
164                       Name: model.name,
165                       Features: model.features,
166                       UserIds: model.userIds,
167                       Quantities: quantities,
168                   };
169         try {
170             setLoading(true);
171             await onSubmitDone(dtoBody);
172             rest.onClose?.();
173         } finally {
174             setLoading(false);
175         }
176     };
178     // Add checked locations in the model
179     const handleUpdateCheckedLocations = (updatedCheckedLocations: GatewayLocation[]) => {
180         setModel((prevModel) => ({
181             ...prevModel,
182             checkedLocations: updatedCheckedLocations,
183         }));
184     };
186     return (
187         <>
188             <ModalTwo size={step === STEP.MEMBERS ? 'xlarge' : 'large'} as={Form} onSubmit={handleSubmit} {...rest}>
189                 <ModalTwoHeader
190                     title={(() => {
191                         if (step === STEP.NAME) {
192                             return isEditing ? c('Action').t`Edit Gateway` : c('Title').t`Create Gateway`;
193                         }
195                         if (step === STEP.COUNTRIES) {
196                             return c('Title').t`Add dedicated servers`;
197                         }
199                         return c('Title').t`Add users`;
200                     })()}
201                 />
202                 <ModalTwoContent>
203                     {deletedDedicatedIPsLoading ? (
204                         <Loader />
205                     ) : (
206                         <>
207                             {step === STEP.NAME && (
208                                 <GatewayNameField model={model} changeModel={changeModel} validator={validator} />
209                             )}
210                             {step === STEP.COUNTRIES && (
211                                 <GatewayCountrySelection
212                                     singleServer={singleServer}
213                                     locations={locations}
214                                     ownedCount={ownedCount}
215                                     usedCount={usedCount}
216                                     addedCount={addedCount}
217                                     deletedDedicatedIPs={deletedDedicatedIPs}
218                                     countryOptions={countryOptions}
219                                     loading={loading}
220                                     model={model}
221                                     onUpdateCheckedLocations={handleUpdateCheckedLocations}
222                                     changeModel={changeModel}
223                                 />
224                             )}
225                             {step === STEP.MEMBERS && (
226                                 <GatewayUserSelection
227                                     loading={loading}
228                                     users={users}
229                                     model={model}
230                                     changeModel={changeModel}
231                                 />
232                             )}
233                         </>
234                     )}
235                 </ModalTwoContent>
236                 <ModalTwoFooter>
237                     {showCancelButton || step !== STEP.NAME ? (
238                         <Button color="weak" onClick={stepBack}>
239                             {step === STEP.NAME
240                                 ? c('Action').t`Cancel`
241                                 : /* button to go back to previous step of gateway creation */ c('Action').t`Back`}
242                         </Button>
243                     ) : (
244                         <div />
245                     )}
246                     <Button color="norm" type="submit" loading={loading} disabled={!canContinue}>
247                         {step === STEP.MEMBERS
248                             ? /* final step submit button of the creation, if not clean translation possible, it can also simply be "Create" */ c(
249                                   'Action'
250                               ).t`Done`
251                             : /* button to continue to the next step of gateway creation */ c('Action').t`Continue`}
252                     </Button>
253                 </ModalTwoFooter>
254             </ModalTwo>
255             {addServerConfirmationModal}
256         </>
257     );
260 export default GatewayModal;