Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / vpn / OpenVPNConfigurationSection / OpenVPNConfigurationSection.tsx
blob1fcc92b60d8ce0414aae1c782f6113c00140af95
1 import { useCallback, useEffect, useMemo, useState } from 'react';
3 import groupBy from 'lodash/groupBy';
4 import { c, msgid } from 'ttag';
6 import { usePlans } from '@proton/account/plans/hooks';
7 import { useUser } from '@proton/account/user/hooks';
8 import { useUserSettings } from '@proton/account/userSettings/hooks';
9 import { ButtonLike, Href } from '@proton/atoms';
10 import Icon from '@proton/components/components/icon/Icon';
11 import Radio from '@proton/components/components/input/Radio';
12 import RadioGroup from '@proton/components/components/input/RadioGroup';
13 import Info from '@proton/components/components/link/Info';
14 import SettingsLink from '@proton/components/components/link/SettingsLink';
15 import SettingsParagraph from '@proton/components/containers/account/SettingsParagraph';
16 import SettingsSectionWide from '@proton/components/containers/account/SettingsSectionWide';
17 import useUserVPN from '@proton/components/hooks/useUserVPN';
18 import useVPNLogicals from '@proton/components/hooks/useVPNLogicals';
19 import { PLANS } from '@proton/payments';
20 import { SORT_DIRECTION, VPN_APP_NAME, VPN_CONNECTIONS, VPN_HOSTNAME } from '@proton/shared/lib/constants';
21 import type { Logical } from '@proton/shared/lib/vpn/Logical';
23 import {
24     type CountryOptions,
25     correctAbbr,
26     getCountryOptions,
27     getLocalizedCountryByAbbr,
28 } from '../../../helpers/countries';
29 import useSortedList from '../../../hooks/useSortedList';
30 import type { EnhancedLogical } from '../OpenVPNConfigurationSection/interface';
31 import ConfigsTable, { CATEGORY } from './ConfigsTable';
32 import ServerConfigs from './ServerConfigs';
33 import { isSecureCoreEnabled, isTorEnabled } from './utils';
35 const PLATFORM = {
36     MACOS: 'macOS',
37     LINUX: 'Linux',
38     WINDOWS: 'Windows',
39     ANDROID: 'Android',
40     IOS: 'iOS',
41     ROUTER: 'Router',
44 const PROTOCOL = {
45     TCP: 'tcp',
46     UDP: 'udp',
49 interface Props {
50     onSelect?: (logical: Logical) => void;
51     selecting?: boolean;
52     listOnly?: boolean;
53     excludedCategories?: CATEGORY[];
54     countryOptions?: CountryOptions;
57 const OpenVPNConfigurationSection = ({
58     countryOptions: maybeCountryOptions,
59     onSelect,
60     selecting,
61     listOnly = false,
62     excludedCategories = [],
63 }: Props) => {
64     const [platform, setPlatform] = useState(PLATFORM.ANDROID);
65     const [protocol, setProtocol] = useState(PROTOCOL.UDP);
66     const [plansResult, loadingPlans] = usePlans();
67     const plans = plansResult?.plans || [];
68     const { loading, result, fetch: fetchLogicals } = useVPNLogicals();
69     const { result: vpnResult, loading: vpnLoading, fetch: fetchUserVPN } = useUserVPN();
70     const [{ hasPaidVpn }] = useUser();
71     const [userSettings] = useUserSettings();
72     const userVPN = vpnResult?.VPN;
73     const maxTier = userVPN?.MaxTier || 0;
74     const [category, setCategory] = useState(CATEGORY.FREE);
75     const excludeCategoryMap = excludedCategories.reduce<{ [key in CATEGORY]?: boolean }>((map, excludedCategory) => {
76         map[excludedCategory] = true;
77         return map;
78     }, {});
80     if (maxTier) {
81         excludeCategoryMap[CATEGORY.FREE] = true;
82     }
84     const selectedCategory = maxTier && category === CATEGORY.FREE ? CATEGORY.SERVER : category;
86     const countryOptions = maybeCountryOptions || getCountryOptions(userSettings);
88     const getIsUpgradeRequired = useCallback(
89         (server: Logical) => {
90             return !userVPN || (!hasPaidVpn && server.Tier > 0);
91         },
92         [userVPN, hasPaidVpn]
93     );
95     const servers = useMemo((): EnhancedLogical[] => {
96         return (result?.LogicalServers || []).map((server) => ({
97             ...server,
98             country: getLocalizedCountryByAbbr(correctAbbr(server.ExitCountry), countryOptions),
99             isUpgradeRequired: getIsUpgradeRequired(server),
100         }));
101     }, [result?.LogicalServers, getIsUpgradeRequired]);
103     const { sortedList: allServers } = useSortedList(servers, { key: 'country', direction: SORT_DIRECTION.ASC });
105     const isUpgradeRequiredForSecureCore = !Object.keys(userVPN || {}).length || !hasPaidVpn;
106     const isUpgradeRequiredForCountries = !Object.keys(userVPN || {}).length || !hasPaidVpn;
108     useEffect(() => {
109         fetchUserVPN(30_000);
110     }, [hasPaidVpn]);
112     const secureCoreServers = useMemo(() => {
113         return allServers
114             .filter(({ Features }) => isSecureCoreEnabled(Features))
115             .map((server) => {
116                 return {
117                     ...server,
118                     isUpgradeRequired: isUpgradeRequiredForSecureCore,
119                 };
120             });
121     }, [allServers, isUpgradeRequiredForSecureCore]);
123     const countryServers = useMemo(() => {
124         return Object.values(
125             groupBy(
126                 allServers.filter(({ Tier, Features }) => {
127                     return Tier === 2 && !isSecureCoreEnabled(Features) && !isTorEnabled(Features);
128                 }),
129                 (a) => a.ExitCountry
130             )
131         ).map((groups) => {
132             const [first] = groups;
133             const activeServers = groups.filter(({ Status }) => Status === 1);
134             const load = activeServers.reduce((acc, { Load }) => acc + (Load || 0), 0) / activeServers.length;
135             return {
136                 ...first,
137                 isUpgradeRequired: isUpgradeRequiredForCountries,
138                 Load: Number.isNaN(load) ? 0 : Math.round(load),
139                 Domain: `${first.EntryCountry.toLowerCase()}.protonvpn.net`, // Forging domain
140                 Servers: groups.flatMap((logical) => logical.Servers || []),
141             };
142         });
143     }, [allServers, isUpgradeRequiredForCountries]);
145     const freeServers = useMemo(() => {
146         return allServers.filter(({ Tier }) => Tier === 0);
147     }, [allServers]);
149     useEffect(() => {
150         if (vpnLoading) {
151             return;
152         }
153         if (!hasPaidVpn || userVPN?.PlanName === 'trial') {
154             setCategory(CATEGORY.FREE);
155         }
156     }, [vpnLoading]);
158     useEffect(() => {
159         void fetchUserVPN(30_000);
160     }, [hasPaidVpn]);
162     useEffect(() => {
163         void fetchLogicals(30_000);
164     }, []);
166     const vpnPlan = plans?.find(({ Name }) => Name === PLANS.VPN);
167     const plusVpnConnections = vpnPlan?.MaxVPN || VPN_CONNECTIONS;
169     const vpnPlus = vpnPlan?.Title;
171     return (
172         <SettingsSectionWide>
173             {!vpnLoading && (
174                 <>
175                     {!listOnly && (
176                         <>
177                             <SettingsParagraph>
178                                 {c('Info')
179                                     .t`These configuration files let you choose which ${VPN_APP_NAME} server you connect to when using a third-party VPN app or setting up a VPN connection on a router.
180                         `}
181                             </SettingsParagraph>
182                             <h3 className="mt-8 mb-2">{c('Title').t`1. Select platform`}</h3>
183                             <div className="flex flex-column md:flex-row">
184                                 {[
185                                     {
186                                         value: PLATFORM.ANDROID,
187                                         link: 'https://protonvpn.com/support/android-vpn-setup/',
188                                         label: c('Option').t`Android`,
189                                     },
190                                     {
191                                         value: PLATFORM.IOS,
192                                         link: 'https://protonvpn.com/support/ios-vpn-setup/',
193                                         label: c('Option').t`iOS`,
194                                     },
195                                     {
196                                         value: PLATFORM.WINDOWS,
197                                         link: 'https://protonvpn.com/support/openvpn-windows-setup/',
198                                         label: c('Option').t`Windows`,
199                                     },
200                                     {
201                                         value: PLATFORM.MACOS,
202                                         link: 'https://protonvpn.com/support/mac-vpn-setup/',
203                                         label: c('Option').t`macOS`,
204                                     },
205                                     {
206                                         value: PLATFORM.LINUX,
207                                         link: 'https://protonvpn.com/support/linux-vpn-setup/',
208                                         label: c('Option').t`GNU/Linux`,
209                                     },
210                                     {
211                                         value: PLATFORM.ROUTER,
212                                         link: 'https://protonvpn.com/support/installing-protonvpn-on-a-router/',
213                                         label: c('Option').t`Router`,
214                                     },
215                                 ].map(({ value, label, link }) => {
216                                     return (
217                                         <div key={value} className="mr-8 mb-4">
218                                             <Radio
219                                                 id={'platform-' + value}
220                                                 onChange={() => setPlatform(value)}
221                                                 checked={platform === value}
222                                                 name="platform"
223                                                 className="flex inline-flex *:self-center mb-2"
224                                             >
225                                                 {label}
226                                             </Radio>
227                                             <Href
228                                                 href={link}
229                                                 className="text-sm m-0 block ml-custom"
230                                                 style={{ '--ml-custom': '1.75rem' }}
231                                             >{c('Link').t`View guide`}</Href>
232                                         </div>
233                                     );
234                                 })}
235                             </div>
237                             <h3 className="mt-8 mb-2">{c('Title').t`2. Select protocol`}</h3>
238                             <div className="flex flex-column md:flex-row mb-2">
239                                 <RadioGroup
240                                     name="protocol"
241                                     value={protocol}
242                                     onChange={setProtocol}
243                                     options={[
244                                         { value: PROTOCOL.UDP, label: c('Option').t`UDP` },
245                                         { value: PROTOCOL.TCP, label: c('Option').t`TCP` },
246                                     ]}
247                                 />
248                             </div>
249                             <div className="mb-4">
250                                 <Href href="https://protonvpn.com/support/udp-tcp/" className="text-sm m-0">{c('Link')
251                                     .t`What is the difference between UDP and TCP protocols?`}</Href>
252                             </div>
254                             <h3 className="mt-8 mb-2">{c('Title').t`3. Select config file and download`}</h3>
255                         </>
256                     )}
257                     <div className="flex flex-column md:flex-row mb-6">
258                         <RadioGroup
259                             name={'category' + (listOnly ? '-list' : '')}
260                             value={selectedCategory}
261                             onChange={setCategory}
262                             options={[
263                                 { value: CATEGORY.COUNTRY, label: c('Option').t`Country configs` },
264                                 { value: CATEGORY.SERVER, label: c('Option').t`Standard server configs` },
265                                 { value: CATEGORY.FREE, label: c('Option').t`Free server configs` },
266                                 { value: CATEGORY.SECURE_CORE, label: c('Option').t`Secure Core configs` },
267                             ].filter((option) => !excludeCategoryMap[option.value])}
268                         />
269                     </div>
270                 </>
271             )}
272             <div className="mb-4">
273                 {selectedCategory === CATEGORY.SECURE_CORE && (
274                     <>
275                         <SettingsParagraph learnMoreUrl="https://protonvpn.com/support/secure-core-vpn">
276                             {c('Info')
277                                 .t`Install a Secure Core configuration file to benefit from an additional protection against VPN endpoint compromise.`}
278                         </SettingsParagraph>
279                         {isUpgradeRequiredForSecureCore && (
280                             <SettingsParagraph>
281                                 <span className="block">{
282                                     // translator: ${vpnPlus} is "VPN Plus" (taken from plan title)
283                                     c('Info').t`${vpnPlus} required for Secure Core feature.`
284                                 }</span>
285                                 <SettingsLink path="/upgrade">{c('Link').t`Learn more`}</SettingsLink>
286                             </SettingsParagraph>
287                         )}
288                         <ConfigsTable
289                             category={CATEGORY.SECURE_CORE}
290                             platform={platform}
291                             protocol={protocol}
292                             loading={loading}
293                             servers={secureCoreServers}
294                             onSelect={onSelect}
295                             selecting={selecting}
296                             countryOptions={countryOptions}
297                         />
298                     </>
299                 )}
300                 {selectedCategory === CATEGORY.COUNTRY && (
301                     <>
302                         {!listOnly && (
303                             <SettingsParagraph>
304                                 {c('Info')
305                                     .t`Install a Country configuration file to connect to a random server in the country of your choice.`}
306                             </SettingsParagraph>
307                         )}
308                         {isUpgradeRequiredForCountries && (
309                             <SettingsParagraph learnMoreUrl={`https://${VPN_HOSTNAME}/dashboard`}>{
310                                 // translator: ${vpnPlus} is "VPN Plus" (taken from plan title)
311                                 // translator: This notice appears when a free user go to "OpenVPN configuration files" section and select "Country configs'
312                                 c('Info').t`${vpnPlus} required for Country level connection.`
313                             }</SettingsParagraph>
314                         )}
315                         <ConfigsTable
316                             category={CATEGORY.COUNTRY}
317                             platform={platform}
318                             protocol={protocol}
319                             loading={loading}
320                             servers={countryServers}
321                             onSelect={onSelect}
322                             selecting={selecting}
323                             countryOptions={countryOptions}
324                         />
325                     </>
326                 )}
327                 {selectedCategory === CATEGORY.SERVER && (
328                     <>
329                         {!listOnly && (
330                             <SettingsParagraph>{c('Info')
331                                 .t`Install a Server configuration file to connect to a specific server in the country of your choice.`}</SettingsParagraph>
332                         )}
333                         <ServerConfigs
334                             category={selectedCategory}
335                             platform={platform}
336                             protocol={protocol}
337                             loading={loading}
338                             servers={allServers}
339                             defaultOpen={false}
340                             onSelect={onSelect}
341                             selecting={selecting}
342                             countryOptions={countryOptions}
343                         />
344                     </>
345                 )}
346                 {selectedCategory === CATEGORY.FREE && (
347                     <>
348                         {!listOnly && (
349                             <SettingsParagraph>
350                                 {c('Info')
351                                     .t`Install a Free server configuration file to connect to a specific server in one of the three free locations.`}
352                             </SettingsParagraph>
353                         )}
354                         <ServerConfigs
355                             countryOptions={countryOptions}
356                             category={selectedCategory}
357                             platform={platform}
358                             protocol={protocol}
359                             loading={loading}
360                             servers={freeServers}
361                             defaultOpen={true}
362                             onSelect={onSelect}
363                             selecting={selecting}
364                         />
365                     </>
366                 )}
367                 {!listOnly && (
368                     <>
369                         {!loadingPlans && (userVPN?.PlanName === 'trial' || !hasPaidVpn) && vpnPlus && (
370                             <div className="border p-7 text-center">
371                                 <h3 className="color-primary mt-0 mb-4">{
372                                     // translator: ${vpnPlus} is "VPN Plus" (taken from plan title)
373                                     c('Title').t`Get ${vpnPlus} to access all servers`
374                                 }</h3>
375                                 <ul className="unstyled inline-flex mt-0 mb-8 flex-column md:flex-row">
376                                     <li className="flex flex-nowrap items-center mr-4">
377                                         <Icon name="checkmark" className="color-success mr-2" />
378                                         <span className="text-bold">{c('Feature').t`Access to all countries`}</span>
379                                     </li>
380                                     <li className="flex flex-nowrap items-center mr-4">
381                                         <Icon name="checkmark" className="color-success mr-2" />
382                                         <span className="text-bold">{c('Feature').t`Secure Core servers`}</span>
383                                     </li>
384                                     <li className="flex flex-nowrap items-center mr-4">
385                                         <Icon name="checkmark" className="color-success mr-2" />
386                                         <span className="text-bold">{c('Feature').t`Fastest VPN servers`}</span>
387                                     </li>
388                                     <li className="flex flex-nowrap items-center mr-4">
389                                         <Icon name="checkmark" className="color-success mr-2" />
390                                         <span className="text-bold">{c('Feature').t`Torrenting support (P2P)`}</span>
391                                     </li>
392                                     <li className="flex flex-nowrap items-center mr-4">
393                                         <Icon name="checkmark" className="color-success mr-2" />
394                                         <span className="text-bold">
395                                             {c('Feature').ngettext(
396                                                 msgid`Connection for up to ${plusVpnConnections} device`,
397                                                 `Connection for up to ${plusVpnConnections} devices`,
398                                                 plusVpnConnections
399                                             )}
400                                         </span>
401                                     </li>
402                                     <li className="flex flex-nowrap items-center ">
403                                         <Icon name="checkmark" className="color-success mr-2" />
404                                         <span className="text-bold mr-2">{c('Feature')
405                                             .t`Secure streaming support`}</span>
406                                         <Info
407                                             url="https://protonvpn.com/support/streaming-guide/"
408                                             title={c('VPN info')
409                                                 .t`Netflix, Amazon Prime Video, BBC iPlayer, ESPN+, Disney+, HBO Now, and more.`}
410                                         />
411                                     </li>
412                                 </ul>
413                                 <div>
414                                     <ButtonLike
415                                         as={SettingsLink}
416                                         color="norm"
417                                         path={`/dashboard?plan=${PLANS.VPN2024}`}
418                                     >
419                                         {
420                                             // translator: ${vpnPlus} is "VPN Plus" (taken from plan title)
421                                             c('Action').t`Get ${vpnPlus}`
422                                         }
423                                     </ButtonLike>
424                                 </div>
425                             </div>
426                         )}
427                     </>
428                 )}
429             </div>
430         </SettingsSectionWide>
431     );
434 export default OpenVPNConfigurationSection;