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>;
36 users: readonly GatewayUser[];
37 countryOptions: CountryOptions;
39 singleServer?: boolean;
40 showCancelButton?: boolean;
41 onSubmitDone: (server: GatewayModel) => Promise<void>;
50 const GatewayModal = ({
60 showCancelButton = false,
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]
83 const specificCountryCount = useSpecificCountryCount(model, remainingCount, deletedInCountries);
84 const needUpsell = useMemo(
85 () => totalCountExceeded || specificCountryCount > 0,
86 [totalCountExceeded, specificCountryCount]
88 const canContinue = useMemo(
89 () => step !== STEP.COUNTRIES || !(needUpsell || (!singleServer && totalAddedCount < 1)),
90 [step, needUpsell, singleServer, totalAddedCount]
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);
102 if (step === STEP.COUNTRIES) {
111 const handleSubmit = async () => {
112 if (!onFormSubmit()) {
116 if (step === STEP.NAME) {
117 setStep(STEP.COUNTRIES);
122 const quantities: Record<string, number> = {};
125 Object.keys(model.quantities || {}).forEach((locationId) => {
126 const count = model.quantities?.[locationId] || 0;
129 quantities[locationId] = count;
134 Object.keys(model.unassignedIpQuantities || {}).forEach((locationId) => {
135 const count = model.unassignedIpQuantities?.[locationId] || 0;
138 quantities[locationId] = (quantities[locationId] || 0) + count;
143 if (step === STEP.COUNTRIES) {
144 showAddServerConfirmationModal({
145 onSubmitDone: () => {
146 setStep(STEP.MEMBERS);
148 totalQuantities: quantities,
155 const dtoBody: GatewayModel =
156 singleServer || total === 1
159 Location: model.location,
160 Features: model.features,
161 UserIds: model.userIds,
165 Features: model.features,
166 UserIds: model.userIds,
167 Quantities: quantities,
171 await onSubmitDone(dtoBody);
178 // Add checked locations in the model
179 const handleUpdateCheckedLocations = (updatedCheckedLocations: GatewayLocation[]) => {
180 setModel((prevModel) => ({
182 checkedLocations: updatedCheckedLocations,
188 <ModalTwo size={step === STEP.MEMBERS ? 'xlarge' : 'large'} as={Form} onSubmit={handleSubmit} {...rest}>
191 if (step === STEP.NAME) {
192 return isEditing ? c('Action').t`Edit Gateway` : c('Title').t`Create Gateway`;
195 if (step === STEP.COUNTRIES) {
196 return c('Title').t`Add dedicated servers`;
199 return c('Title').t`Add users`;
203 {deletedDedicatedIPsLoading ? (
207 {step === STEP.NAME && (
208 <GatewayNameField model={model} changeModel={changeModel} validator={validator} />
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}
221 onUpdateCheckedLocations={handleUpdateCheckedLocations}
222 changeModel={changeModel}
225 {step === STEP.MEMBERS && (
226 <GatewayUserSelection
230 changeModel={changeModel}
237 {showCancelButton || step !== STEP.NAME ? (
238 <Button color="weak" onClick={stepBack}>
240 ? c('Action').t`Cancel`
241 : /* button to go back to previous step of gateway creation */ c('Action').t`Back`}
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(
251 : /* button to continue to the next step of gateway creation */ c('Action').t`Continue`}
255 {addServerConfirmationModal}
260 export default GatewayModal;