Skip to content
Snippets Groups Projects
ManagementApp.jsx 6.23 KiB
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { Navigate, BrowserRouter, Route, Routes } from 'react-router-dom';

import { useActions } from '../../hooks/useActions';
import { theme } from '../../style';
import { tokens } from '../../tokens';
import { ExposeNavigate } from '../../util/router-tools';
import { Text } from '../common/Text';
import { View } from '../common/View';
import { LoggedInUser } from '../LoggedInUser';
import { Notifications } from '../Notifications';
import { useServerVersion } from '../ServerContext';

import { BudgetList } from './BudgetList';
import { ConfigServer } from './ConfigServer';
import { Modals } from './Modals';
import { ServerURL } from './ServerURL';
import { Bootstrap } from './subscribe/Bootstrap';
import { ChangePassword } from './subscribe/ChangePassword';
import { Error } from './subscribe/Error';
import { Login } from './subscribe/Login';
import { WelcomeScreen } from './WelcomeScreen';

function Version() {
  const version = useServerVersion();

  return (
    <Text
      style={{
        color: theme.pageTextSubdued,
        ':hover': { color: theme.pageText },
        margin: 15,
        marginLeft: 17,
        [`@media (min-width: ${tokens.breakpoint_small})`]: {
          position: 'absolute',
          bottom: 0,
          right: 0,
          marginLeft: 15,
          marginRight: 17,
          zIndex: 5001,
        },
      }}
    >
      {`App: v${window.Actual?.ACTUAL_VERSION} | Server: ${version}`}
    </Text>
  );
}

export function ManagementApp({ isLoading }) {
  const files = useSelector(state => state.budgets.allFiles);
  const userData = useSelector(state => state.user.data);
  const managerHasInitialized = useSelector(
    state => state.app.managerHasInitialized,
  );

  const { setAppState, getUserData, loadAllFiles } = useActions();

  // runs on mount only
  useEffect(() => {
    // An action may have been triggered from outside, and we don't
    // want to override its loading message so we only show the
    // initial loader if there isn't already a message

    // Remember: this component is remounted every time the user
    // closes a budget. That's why we keep `managerHasInitialized` in
    // redux so that it persists across renders. This will show the
    // loading spinner on first run, but never again since we'll have
    // a cached list of files and can show them
    if (!managerHasInitialized) {
      if (!isLoading) {
        setAppState({ loadingText: '' });
      }
    }

    async function fetchData() {
      const userData = await getUserData();
      if (userData) {
        await loadAllFiles();
      }

      // TODO: There is a race condition here. The user could perform an
      // action that starts loading in between where `isLoading`
      // was captured and this would clear it. We really only want to
      // ever clear the initial loading screen, so we need a "loading
      // id" of some kind.
      setAppState({
        managerHasInitialized: true,
        ...(!isLoading ? { loadingText: null } : null),
      });
    }

    fetchData();
  }, []);

  if (!managerHasInitialized) {
    return null;
  }

  if (isLoading) {
    return null;
  }

  return (
    <BrowserRouter>
      <ExposeNavigate />
      <View style={{ height: '100%', color: theme.pageText }}>
        <View
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            height: 40,
            WebkitAppRegion: 'drag',
          }}
        />
        <View
          style={{
            position: 'absolute',
            bottom: 40,
            right: 15,
          }}
        >
          <Notifications
            style={{
              position: 'relative',
              left: 'initial',
              right: 'initial',
            }}
          />
        </View>

        {managerHasInitialized && (
          <View
            style={{
              alignItems: 'center',
              bottom: 0,
              justifyContent: 'center',
              left: 0,
              padding: 20,
              position: 'absolute',
              right: 0,
              top: 0,
            }}
          >
            {userData && files ? (
              <>
                <Routes>
                  <Route path="/config-server" element={<ConfigServer />} />

                  <Route path="/change-password" element={<ChangePassword />} />
                  {files && files.length > 0 ? (
                    <Route path="/" element={<BudgetList />} />
                  ) : (
                    <Route path="/" element={<WelcomeScreen />} />
                  )}
                  {/* Redirect all other pages to this route */}
                  <Route path="/*" element={<Navigate to="/" />} />
                </Routes>

                <View
                  style={{
                    position: 'absolute',
                    top: 0,
                    right: 0,
                    padding: '6px 10px',
                    zIndex: 4000,
                  }}
                >
                  <Routes>
                    <Route path="/config-server" element={null} />
                    <Route
                      path="/*"
                      element={
                        <LoggedInUser
                          hideIfNoServer
                          style={{ padding: '4px 7px' }}
                        />
                      }
                    />
                  </Routes>
                </View>
              </>
            ) : (
              <Routes>
                <Route path="/login" element={<Login />} />
                <Route path="/error" element={<Error />} />
                <Route path="/config-server" element={<ConfigServer />} />
                <Route path="/bootstrap" element={<Bootstrap />} />
                {/* Redirect all other pages to this route */}
                <Route
                  path="/*"
                  element={<Navigate to="/bootstrap" replace />}
                />
              </Routes>
            )}
          </View>
        )}

        <Routes>
          <Route path="/config-server" element={null} />
          <Route path="/*" element={<ServerURL />} />
        </Routes>
        <Version />
      </View>
      <Modals />
    </BrowserRouter>
  );
}