1 import { useMemo, useState } from 'react';
3 import { c } from 'ttag';
5 import { Button } from '@proton/atoms';
6 import Form from '@proton/components/components/form/Form';
7 import Loader from '@proton/components/components/loader/Loader';
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 { useModalTwoStatic } from '@proton/components/components/modalTwo/useModalTwo';
14 import useFormErrors from '@proton/components/components/v2/useFormErrors';
15 import useApiResult from '@proton/components/hooks/useApiResult';
17 import type { CountryOptions } from '../../../helpers/countries';
18 import AddServerConfirmationModal from './AddServerConfirmationModal';
19 import type { DeletedDedicatedIp } from './DeletedDedicatedIp';
20 import { GatewayCountrySelection } from './GatewayCountrySelection';
21 import type { GatewayDto } from './GatewayDto';
22 import type { GatewayLocation } from './GatewayLocation';
23 import type { GatewayModel } from './GatewayModel';
24 import { GatewayNameField } from './GatewayNameField';
25 import type { GatewayUser } from './GatewayUser';
26 import { GatewayUserSelection } from './GatewayUserSelection';
27 import { queryDeletedDedicatedIPs } from './api';
28 import { getInitialModel } from './helpers';
29 import { useAddedQuantities, useUnassigningAddedQuantities } from './useAddedQuantities';
30 import { useSpecificCountryCount } from './useSpecificCountryCount';
32 interface Props extends ModalProps<typeof Form> {
33 locations: readonly GatewayLocation[];
34 deletedInCountries: Record<string, number>;
37 users: readonly GatewayUser[];
38 countryOptions: CountryOptions;
40 singleServer?: boolean;
41 showCancelButton?: boolean;
42 onSubmitDone: (server: GatewayModel) => Promise<void>;
51 const GatewayModal = ({
61 showCancelButton = false,
64 const { loading: deletedDedicatedIPsLoading, result } = useApiResult<
65 { DedicatedIps: DeletedDedicatedIp[] },
66 typeof queryDeletedDedicatedIPs
67 >(queryDeletedDedicatedIPs, []);
69 const deletedDedicatedIPs = result?.DedicatedIps;
71 const [addServerConfirmationModal, showAddServerConfirmationModal] = useModalTwoStatic(AddServerConfirmationModal);
73 const { validator, onFormSubmit } = useFormErrors();
74 const [step, setStep] = useState(STEP.NAME);
75 const [model, setModel] = useState(getInitialModel(locations));
76 const [loading, setLoading] = useState(false);
77 const remainingCount = useMemo(() => ownedCount - usedCount, [ownedCount, usedCount]);
78 const addedCount = useAddedQuantities(model);
79 const totalAddedCount = addedCount + useUnassigningAddedQuantities(model);
80 const totalCountExceeded = useMemo(
81 () => addedCount > remainingCount - (deletedDedicatedIPs?.length || 0),
82 [addedCount, remainingCount]
84 const specificCountryCount = useSpecificCountryCount(model, remainingCount, deletedInCountries);
85 const needUpsell = useMemo(
86 () => totalCountExceeded || specificCountryCount > 0,
87 [totalCountExceeded, specificCountryCount]
89 const canContinue = useMemo(
90 () => step !== STEP.COUNTRIES || !(needUpsell || (!singleServer && totalAddedCount < 1)),
91 [step, needUpsell, singleServer, totalAddedCount]
94 const changeModel = (diff: Partial<GatewayDto>) => setModel((model: GatewayDto) => ({ ...model, ...diff }));
96 const stepBack = () => {
97 if (step === STEP.MEMBERS) {
98 setStep(locations.length > 1 ? STEP.COUNTRIES : STEP.NAME);
103 if (step === STEP.COUNTRIES) {
112 const handleSubmit = async () => {
113 if (!onFormSubmit()) {
117 if (step === STEP.NAME) {
118 setStep(STEP.COUNTRIES);
123 const quantities: Record<string, number> = {};
126 Object.keys(model.quantities || {}).forEach((locationId) => {
127 const count = model.quantities?.[locationId] || 0;
130 quantities[locationId] = count;
135 Object.keys(model.unassignedIpQuantities || {}).forEach((locationId) => {
136 const count = model.unassignedIpQuantities?.[locationId] || 0;
139 quantities[locationId] = (quantities[locationId] || 0) + count;
144 if (step === STEP.COUNTRIES) {
145 showAddServerConfirmationModal({
146 onSubmitDone: () => {
147 setStep(STEP.MEMBERS);
149 totalQuantities: quantities,
156 const dtoBody: GatewayModel =
157 singleServer || total === 1
160 Location: model.location,
161 Features: model.features,
162 UserIds: model.userIds,
166 Features: model.features,
167 UserIds: model.userIds,
168 Quantities: quantities,
172 await onSubmitDone(dtoBody);
179 // Add checked locations in the model
180 const handleUpdateCheckedLocations = (updatedCheckedLocations: GatewayLocation[]) => {
181 setModel((prevModel) => ({
183 checkedLocations: updatedCheckedLocations,
189 <ModalTwo size={step === STEP.MEMBERS ? 'xlarge' : 'large'} as={Form} onSubmit={handleSubmit} {...rest}>
192 if (step === STEP.NAME) {
193 return isEditing ? c('Action').t`Edit Gateway` : c('Title').t`Create Gateway`;
196 if (step === STEP.COUNTRIES) {
197 return c('Title').t`Add dedicated servers`;
200 return c('Title').t`Add users`;
204 {deletedDedicatedIPsLoading ? (
208 {step === STEP.NAME && (
209 <GatewayNameField model={model} changeModel={changeModel} validator={validator} />
211 {step === STEP.COUNTRIES && (
212 <GatewayCountrySelection
213 singleServer={singleServer}
214 locations={locations}
215 ownedCount={ownedCount}
216 usedCount={usedCount}
217 addedCount={addedCount}
218 deletedDedicatedIPs={deletedDedicatedIPs}
219 countryOptions={countryOptions}
222 onUpdateCheckedLocations={handleUpdateCheckedLocations}
223 changeModel={changeModel}
226 {step === STEP.MEMBERS && (
227 <GatewayUserSelection
231 changeModel={changeModel}
238 {showCancelButton || step !== STEP.NAME ? (
239 <Button color="weak" onClick={stepBack}>
241 ? c('Action').t`Cancel`
242 : /* button to go back to previous step of gateway creation */ c('Action').t`Back`}
247 <Button color="norm" type="submit" loading={loading} disabled={!canContinue}>
248 {step === STEP.MEMBERS
249 ? /* final step submit button of the creation, if not clean translation possible, it can also simply be "Create" */ c(
252 : /* button to continue to the next step of gateway creation */ c('Action').t`Continue`}
256 {addServerConfirmationModal}
261 export default GatewayModal;