summaryrefslogtreecommitdiffhomepage
path: root/desktop/packages/mullvad-vpn/src/renderer/components/StateTriggeredNavigation.tsx
blob: 3ecc36c60bf0576584e54bb9d1ebc71439870816 (plain)
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
import { useEffect, useMemo, useRef } from 'react';

import { RoutePath } from '../../shared/routes';
import { useScheduler } from '../../shared/scheduler';
import { getNavigationBase } from '../lib/functions/navigation-base';
import { TransitionType, useHistory } from '../lib/history';
import { useEffectEvent } from '../lib/utility-hooks';
import { useSelector } from '../redux/store';

export default function StateTriggeredNavigation() {
  const { location, reset } = useHistory();

  const connectedToDaemon = useSelector((state) => state.userInterface.connectedToDaemon);
  const loginState = useSelector((state) => state.account.status);

  const delayScheduler = useScheduler();

  const prevPath = useRef<RoutePath>(getNavigationBase(connectedToDaemon, loginState));
  const nextPath = useMemo(
    () => getNavigationBase(connectedToDaemon, loginState),
    [connectedToDaemon, loginState],
  );

  const updatePath = useEffectEvent((nextPath: RoutePath) => {
    const currentPath = location.pathname as RoutePath;

    if (currentPath !== nextPath) {
      delayScheduler.cancel();

      const transition = getNavigationTransition(currentPath, nextPath);
      const delay = getNavigationDelay(currentPath, nextPath);

      const navigate = () => {
        reset(nextPath, { transition });
      };

      if (delay) {
        delayScheduler.schedule(navigate, delay);
      } else {
        navigate();
      }
    }
  });

  useEffect(() => {
    if (nextPath !== prevPath.current) {
      prevPath.current = nextPath;
      updatePath(nextPath);
    }
    // eslint-disable-next-line react-compiler/react-compiler
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [nextPath]);

  return null;
}

function getNavigationDelay(currentPath: RoutePath, nextPath: RoutePath): number | void {
  if (
    currentPath === RoutePath.login &&
    (nextPath === RoutePath.main || nextPath === RoutePath.expired)
  ) {
    return 1000;
  }
}

function getNavigationTransition(currentPath: RoutePath, nextPath: RoutePath) {
  // First level contains the possible next locations and the second level contains the
  // possible current locations.
  const navigationTransitions: Partial<
    Record<RoutePath, Partial<Record<RoutePath | '*', TransitionType>>>
  > = {
    [RoutePath.launch]: {
      [RoutePath.login]: TransitionType.pop,
      [RoutePath.main]: TransitionType.pop,
      '*': TransitionType.dismiss,
    },
    [RoutePath.login]: {
      [RoutePath.launch]: TransitionType.push,
      [RoutePath.main]: TransitionType.pop,
      [RoutePath.deviceRevoked]: TransitionType.pop,
      [RoutePath.tooManyDevices]: TransitionType.pop,
      '*': TransitionType.dismiss,
    },
    [RoutePath.main]: {
      [RoutePath.launch]: TransitionType.push,
      [RoutePath.login]: TransitionType.push,
      [RoutePath.tooManyDevices]: TransitionType.push,
      '*': TransitionType.dismiss,
    },
    [RoutePath.expired]: {
      [RoutePath.launch]: TransitionType.push,
      [RoutePath.login]: TransitionType.push,
      [RoutePath.tooManyDevices]: TransitionType.push,
      '*': TransitionType.dismiss,
    },
    [RoutePath.timeAdded]: {
      [RoutePath.expired]: TransitionType.push,
      [RoutePath.redeemVoucher]: TransitionType.push,
      '*': TransitionType.dismiss,
    },
    [RoutePath.deviceRevoked]: {
      '*': TransitionType.pop,
    },
    [RoutePath.tooManyDevices]: {
      [RoutePath.login]: TransitionType.push,
    },
  };

  return navigationTransitions[nextPath]?.[currentPath] ?? navigationTransitions[nextPath]?.['*'];
}