Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split dashboard routes into individual pages #10632

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion components/dashboard/AccountSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Avatar from '../Avatar';
import Container from '../Container';
import { Flex } from '../Grid';
import Link from '../Link';
import LoadingPlaceholder from '../LoadingPlaceholder';
import { Dropdown, DropdownContent } from '../StyledDropdown';
import StyledHr from '../StyledHr';
import StyledRoundButton from '../StyledRoundButton';
Expand Down Expand Up @@ -255,7 +256,16 @@ const AccountSwitcher = ({ activeSlug }: { activeSlug: string }) => {
<React.Fragment>
<Flex alignItems="center" flex={0}>
<DropdownButton {...triggerProps}>
{activeSlug === ROOT_PROFILE_KEY ? <RootOption /> : <Option collective={activeAccount} />}
{!activeSlug ? (
<div className="flex w-full flex-col gap-2">
<LoadingPlaceholder height={14} width="100%" />
<LoadingPlaceholder height={12} width="100%" />
</div>
) : activeSlug === ROOT_PROFILE_KEY ? (
<RootOption />
) : (
<Option collective={activeAccount} />
)}
<ChevronsUpDown size={18} />
</DropdownButton>
</Flex>
Expand Down
161 changes: 58 additions & 103 deletions pages/dashboard.tsx → components/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import React from 'react';
import { useQuery } from '@apollo/client';
import { clsx } from 'clsx';
import { useRouter } from 'next/router';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';

import { isHostAccount, isIndividualAccount } from '../lib/collective';
import roles from '../lib/constants/roles';
import { API_V2_CONTEXT } from '../lib/graphql/helpers';
import useLocalStorage from '../lib/hooks/useLocalStorage';
import useLoggedInUser from '../lib/hooks/useLoggedInUser';
import { LOCAL_STORAGE_KEYS } from '../lib/local-storage';
import { require2FAForAdmins } from '../lib/policies';
import { PREVIEW_FEATURE_KEYS } from '../lib/preview-features';
import roles from '../../lib/constants/roles';
import { API_V2_CONTEXT } from '../../lib/graphql/helpers';
import useLoggedInUser from '../../lib/hooks/useLoggedInUser';
import { require2FAForAdmins } from '../../lib/policies';
import { PREVIEW_FEATURE_KEYS } from '../../lib/preview-features';

import {
ALL_SECTIONS,
ROOT_PROFILE_ACCOUNT,
ROOT_PROFILE_KEY,
ROOT_SECTIONS,
SECTIONS_ACCESSIBLE_TO_ACCOUNTANTS,
} from '../components/dashboard/constants';
import { DashboardContext } from '../components/dashboard/DashboardContext';
import DashboardSection from '../components/dashboard/DashboardSection';
import { getMenuItems } from '../components/dashboard/Menu';
import DashboardTopBar from '../components/dashboard/preview/DashboardTopBar';
import SubMenu from '../components/dashboard/preview/SubMenu';
import { adminPanelQuery } from '../components/dashboard/queries';
import AdminPanelSideBar from '../components/dashboard/SideBar';
import Link from '../components/Link';
import MessageBox from '../components/MessageBox';
import Footer from '../components/navigation/Footer';
import NotificationBar from '../components/NotificationBar';
import Page from '../components/Page';
import SignInOrJoinFree from '../components/SignInOrJoinFree';
import { TwoFactorAuthRequiredMessage } from '../components/TwoFactorAuthRequiredMessage';
} from '../../components/dashboard/constants';
import { DashboardContext } from '../../components/dashboard/DashboardContext';
import { getMenuItems } from '../../components/dashboard/Menu';
import DashboardTopBar from '../../components/dashboard/preview/DashboardTopBar';
import SubMenu from '../../components/dashboard/preview/SubMenu';
import { adminPanelQuery } from '../../components/dashboard/queries';
import AdminPanelSideBar from '../../components/dashboard/SideBar';
import Link from '../../components/Link';
import MessageBox from '../../components/MessageBox';
import Footer from '../../components/navigation/Footer';
import NotificationBar from '../../components/NotificationBar';
import Page from '../../components/Page';
import SignInOrJoinFree from '../../components/SignInOrJoinFree';
import { TwoFactorAuthRequiredMessage } from '../../components/TwoFactorAuthRequiredMessage';

import { OCFBannerWithData } from '../OCFBanner';

import type { DashboardSectionProps } from './types';

const messages = defineMessages({
collectiveIsArchived: {
Expand All @@ -54,26 +51,11 @@ const messages = defineMessages({
},
});

const getDefaultSectionForAccount = (account, loggedInUser) => {
const getNotification = (intl, account) => {
if (!account) {
return null;
} else if (account.type === 'ROOT') {
return ROOT_SECTIONS.ALL_COLLECTIVES;
} else if (
isIndividualAccount(account) ||
(!isHostAccount(account) && loggedInUser.hasPreviewFeatureEnabled(PREVIEW_FEATURE_KEYS.COLLECTIVE_OVERVIEW))
) {
return ALL_SECTIONS.OVERVIEW;
} else if (isHostAccount(account)) {
return ALL_SECTIONS.HOST_EXPENSES;
} else {
const isAdmin = loggedInUser?.isAdminOfCollective(account);
const isAccountant = loggedInUser?.hasRole(roles.ACCOUNTANT, account);
return !isAdmin && isAccountant ? ALL_SECTIONS.PAYMENT_RECEIPTS : ALL_SECTIONS.EXPENSES;
return;
}
};

const getNotification = (intl, account) => {
if (account?.isArchived) {
if (account.type === 'USER') {
return {
Expand All @@ -93,8 +75,10 @@ const getNotification = (intl, account) => {
}
};

function getBlocker(LoggedInUser, account, section) {
if (!LoggedInUser) {
function getBlocker(LoggedInUser, activeSlug, account, section) {
if (!activeSlug) {
return;
} else if (!LoggedInUser) {
return <FormattedMessage id="mustBeLoggedIn" defaultMessage="You must be logged in to see this page" />;
} else if (!account) {
return <FormattedMessage defaultMessage="This account doesn't exist" id="3ABdi3" />;
Expand All @@ -120,33 +104,28 @@ function getBlocker(LoggedInUser, account, section) {
}
}

function getSingleParam(queryParam: string | string[]): string {
return Array.isArray(queryParam) ? queryParam[0] : queryParam;
}

function getAsArray(queryParam: string | string[]): string[] {
return Array.isArray(queryParam) ? queryParam : [queryParam];
function setLastWorkspaceVisit(value: { slug: string }) {
if (typeof document !== 'undefined') {
const val = JSON.stringify(value);
document.cookie = `lastWorkspaceVisit=${val};Max-Age=9999999;secure;path=/`;
}
}

const parseQuery = query => {
return {
slug: getSingleParam(query.slug),
section: getSingleParam(query.section),
subpath: getAsArray(query.subpath)?.filter(Boolean),
};
};

const DashboardPage = () => {
export default function DashboardPage(props: {
Component: React.FC<DashboardSectionProps>;
slug: string;
section: string;
subpath?: string[];
isIndex?: boolean;
}) {
const intl = useIntl();
const router = useRouter();
const { slug, section, subpath } = parseQuery(router.query);
const slug = props.slug;

const section = props.section;
const subpath = props.subpath;
const { LoggedInUser, loadingLoggedInUser } = useLoggedInUser();
const [lastWorkspaceVisit, setLastWorkspaceVisit] = useLocalStorage(LOCAL_STORAGE_KEYS.DASHBOARD_NAVIGATION_STATE, {
slug: LoggedInUser?.collective.slug,
});
const isRootUser = LoggedInUser?.isRoot;
const defaultSlug = lastWorkspaceVisit.slug || LoggedInUser?.collective.slug;
const activeSlug = slug || defaultSlug;
const activeSlug = slug;
const isRootProfile = activeSlug === ROOT_PROFILE_KEY;

const { data, loading } = useQuery(adminPanelQuery, {
Expand All @@ -155,38 +134,31 @@ const DashboardPage = () => {
skip: !activeSlug || !LoggedInUser || isRootProfile,
});
const account = isRootProfile && isRootUser ? ROOT_PROFILE_ACCOUNT : data?.account;
const selectedSection = section || getDefaultSectionForAccount(account, LoggedInUser);
const selectedSection = section;

const useDynamicTopBar = LoggedInUser?.hasPreviewFeatureEnabled(PREVIEW_FEATURE_KEYS.DYNAMIC_TOP_BAR);

// Keep track of last visited workspace account and sections
React.useEffect(() => {
if (activeSlug && activeSlug !== lastWorkspaceVisit.slug) {
if (activeSlug) {
if (LoggedInUser && !useDynamicTopBar) {
// this is instead configured as "default" account in NewAccountSwitcher
setLastWorkspaceVisit({ slug: activeSlug });
}
}
// If there is no slug set (that means /dashboard)
// And if there is an activeSlug (this means lastWorkspaceVisit OR LoggedInUser)
// And a LoggedInUser
// And if activeSlug is different than LoggedInUser slug
if (!slug && activeSlug && LoggedInUser && activeSlug !== LoggedInUser.collective.slug) {
router.replace(`/dashboard/${activeSlug}`);
}
}, [activeSlug, LoggedInUser]);
}, [activeSlug, LoggedInUser, useDynamicTopBar]);

// Clear last visited workspace account if not admin
React.useEffect(() => {
if (account && !LoggedInUser.isAdminOfCollective(account)) {
setLastWorkspaceVisit({ slug: null });
}
}, [account]);
}, [account, LoggedInUser]);

const notification = getNotification(intl, account);
const [expandedSection, setExpandedSection] = React.useState(null);
const isLoading = loading || loadingLoggedInUser;
const blocker = !isLoading && getBlocker(LoggedInUser, account, selectedSection);
const blocker = !isLoading && getBlocker(LoggedInUser, activeSlug, account, selectedSection);
const titleBase = intl.formatMessage({ id: 'Dashboard', defaultMessage: 'Dashboard' });
const menuItems = account ? getMenuItems({ intl, account, LoggedInUser }) : [];
const accountIdentifier = account && (account.name || `@${account.slug}`);
Expand All @@ -208,7 +180,7 @@ const DashboardPage = () => {
setExpandedSection,
account,
activeSlug,
defaultSlug,
defaultSlug: slug,
setDefaultSlug: slug => setLastWorkspaceVisit({ slug }),
}}
>
Expand Down Expand Up @@ -243,12 +215,10 @@ const DashboardPage = () => {
<TwoFactorAuthRequiredMessage className="lg:mt-16" />
) : (
<div className="min-w-0 max-w-screen-xl flex-1">
<DashboardSection
section={selectedSection}
isLoading={isLoading}
account={account}
subpath={subpath}
/>
{!isRootProfile && (
<OCFBannerWithData isDashboard collective={account} hideNextSteps={section === 'host'} />
)}
<props.Component isDashboard accountSlug={slug} subpath={subpath} />
</div>
)}
</div>
Expand Down Expand Up @@ -280,12 +250,7 @@ const DashboardPage = () => {
<div />
)}

<DashboardSection
section={selectedSection}
isLoading={isLoading}
account={account}
subpath={subpath}
/>
<props.Component isDashboard accountSlug={slug} subpath={subpath} />
</div>
)}
</div>
Expand All @@ -295,14 +260,4 @@ const DashboardPage = () => {
</div>
</DashboardContext.Provider>
);
};

DashboardPage.getInitialProps = () => {
return {
scripts: { googleMaps: true }, // TODO: This should be enabled only for events
};
};

// next.js export
// ts-unused-exports:disable-next-line
export default DashboardPage;
}
Loading
Loading