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

feat: Add new Admin Panel #6881

Merged
merged 14 commits into from Sep 27, 2021
9 changes: 6 additions & 3 deletions components/Grid.js
Expand Up @@ -25,9 +25,12 @@ Box.propTypes = {
...propTypes.flexbox,
};

export const Flex = styled(Box)({
display: 'flex',
});
export const Flex = styled(Box)(
{
display: 'flex',
},
compose(space, layout, flexbox),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally liked that we matched the original behavior of the rebass/grid library, and that we're keeping it simple. Why not use the Container, which supports all these?

Copy link
Contributor Author

@kewitz kewitz Sep 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we were matching the rebass behavior. I'm pretty much sure you can pass alignItems="center" overflow="hidden" props in rebass.
I'll refactor this to use Container.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It matches the rebass behavior from when we removed the dependency. This file was initially just a copy-paste of https://github.com/rebassjs/grid/blob/master/src/index.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it weird that a div Flex component accepts no CSS flex or div properties?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kewitz It does accept CSS flex properties already, since it inherits Box which implements styled-system's flexbox. Actually, the properties that you added there are already supported by the component.

);

Flex.displayName = 'Flex';

Expand Down
3 changes: 2 additions & 1 deletion components/ProfileMenuMemberships.js
Expand Up @@ -8,6 +8,7 @@ import styled from 'styled-components';

import { CollectiveType } from '../lib/constants/collectives';
import { isPastEvent } from '../lib/events';
import { getSettingsRoute } from '../lib/url-helpers';

import Avatar from './Avatar';
import { Box, Flex } from './Grid';
Expand Down Expand Up @@ -60,7 +61,7 @@ const MembershipLine = ({ user, membership }) => {
{Boolean(user?.canEditCollective(membership.collective)) && (
<StyledLink
as={Link}
href={`/${membership.collective.slug}/edit`}
href={getSettingsRoute(membership.collective, user)}
kewitz marked this conversation as resolved.
Show resolved Hide resolved
ml={1}
color="black.500"
title={intl.formatMessage(messages.settings)}
Expand Down
3 changes: 2 additions & 1 deletion components/TopBarProfileMenu.js
Expand Up @@ -10,6 +10,7 @@ import { createGlobalStyle } from 'styled-components';

import { API_V2_CONTEXT, gqlV2 } from '../lib/graphql/helpers';
import { getFromLocalStorage, LOCAL_STORAGE_KEYS } from '../lib/local-storage';
import { getSettingsRoute } from '../lib/url-helpers';

import ChangelogTrigger from './changelog/ChangelogTrigger';
import Avatar from './Avatar';
Expand Down Expand Up @@ -79,7 +80,7 @@ const UserAccountLinks = ({ setShowNewsAndUpdates, LoggedInUser, isMobileView, l
) : null
}
</Query>
<UserMenuLinkEntry isMobileMenuLink={isMobileView} href={`/${LoggedInUser.collective.slug}/edit`}>
<UserMenuLinkEntry isMobileMenuLink={isMobileView} href={getSettingsRoute(LoggedInUser.collective, LoggedInUser)}>
<FormattedMessage id="Settings" defaultMessage="Settings" />
</UserMenuLinkEntry>
<UserMenuLinkEntry isMobileMenuLink={isMobileView} href={`/${LoggedInUser.username}/recurring-contributions`}>
Expand Down
Expand Up @@ -15,7 +15,7 @@ import { P } from '../Text';

const messages = defineMessages({
collectives: {
id: 'pricingTable.row.collectives',
id: 'Collectives',
defaultMessage: 'Collectives',
},
managed: {
Expand Down
85 changes: 85 additions & 0 deletions components/admin-panel/AdminPanelSection.js
@@ -0,0 +1,85 @@
import React from 'react';
import PropTypes from 'prop-types';
import { values } from 'lodash';
import { useIntl } from 'react-intl';
import styled from 'styled-components';

import Container from '../Container';
import { Box } from '../Grid';
import HostDashboardExpenses from '../host-dashboard/HostDashboardExpenses';
import HostDashboardHostedCollectives from '../host-dashboard/HostDashboardHostedCollectives';
import PendingApplications from '../host-dashboard/PendingApplications';
import Loading from '../Loading';
import NotFound from '../NotFound';

import AccountSettings from './sections/AccountSettings';
import FinancialContributions from './sections/FinancialContributions';
import { HOST_DASHBOARD_SECTIONS, LEGACY_COLLECTIVE_SETTINGS_SECTIONS, SECTION_LABELS } from './constants';

const HOST_ADMIN_SECTIONS = {
[HOST_DASHBOARD_SECTIONS.HOSTED_COLLECTIVES]: HostDashboardHostedCollectives,
[HOST_DASHBOARD_SECTIONS.FINANCIAL_CONTRIBUTIONS]: FinancialContributions,
[HOST_DASHBOARD_SECTIONS.EXPENSES]: HostDashboardExpenses,
[HOST_DASHBOARD_SECTIONS.PENDING_APPLICATIONS]: PendingApplications,
};

const Title = styled(Box)`
font-size: 24px;
font-weight: 700;
line-height: 32px;
`;

const AdminPanelSection = ({ collective, isLoading, section }) => {
const { formatMessage } = useIntl();

if (isLoading) {
return (
<Container display="flex" justifyContent="center" alignItems="center">
<Loading />
</Container>
);
}

// Host Dashboard
const AdminSectionComponent = HOST_ADMIN_SECTIONS[section];
if (AdminSectionComponent) {
return (
<Container width="100%">
<AdminSectionComponent hostSlug={collective.slug} />
</Container>
);
}

// Form
if (values(LEGACY_COLLECTIVE_SETTINGS_SECTIONS).includes(section)) {
return (
<Container width="100%">
{SECTION_LABELS[section] && (
<Box mb={3}>
<Title>{formatMessage(SECTION_LABELS[section])}</Title>
</Box>
)}
<AccountSettings account={collective} />
</Container>
);
}

return (
<Container display="flex" justifyContent="center" alignItems="center">
<NotFound />
</Container>
);
};

AdminPanelSection.propTypes = {
isLoading: PropTypes.bool,
section: PropTypes.string,
/** The account. Can be null if isLoading is true */
collective: PropTypes.shape({
slug: PropTypes.string.isRequired,
name: PropTypes.string,
isHost: PropTypes.bool,
}),
};

export default AdminPanelSection;
185 changes: 185 additions & 0 deletions components/admin-panel/Menu.js
@@ -0,0 +1,185 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';

import hasFeature, { FEATURES } from '../../lib/allowed-features';
import { CollectiveType, isHost, isOneOfTypes, isType } from '../../lib/collective-sections';

import { HOST_SECTIONS } from '../host-dashboard/constants';

import {
ABOUT_ORG_SECTIONS,
ALL_SECTIONS,
COLLECTIVE_SECTIONS,
FISCAL_HOST_SECTIONS,
ORG_BUDGET_SECTIONS,
} from './constants';
import { MenuGroup, MenuLink, MenuSectionHeader, useSubmenu } from './MenuComponents';

const { USER, ORGANIZATION, COLLECTIVE, FUND, EVENT, PROJECT } = CollectiveType;

const Menu = ({ collective }) => {
const { menuContent, SubMenu } = useSubmenu();

if (menuContent) {
return menuContent;
} else {
return (
<React.Fragment>
<MenuGroup if={isHost(collective)} mb={24}>
<MenuSectionHeader>
<FormattedMessage id="HostDashboard" defaultMessage="Host Dashboard" />
</MenuSectionHeader>
<MenuLink collective={collective} section={HOST_SECTIONS.EXPENSES} />
<MenuLink collective={collective} section={HOST_SECTIONS.FINANCIAL_CONTRIBUTIONS} />
<MenuLink collective={collective} section={HOST_SECTIONS.PENDING_APPLICATIONS} />
<MenuLink collective={collective} section={HOST_SECTIONS.HOSTED_COLLECTIVES} />
</MenuGroup>
<MenuGroup if={isHost(collective) || isType(collective, ORGANIZATION)}>
<MenuSectionHeader>
<FormattedMessage id="Settings" defaultMessage="Settings" />
</MenuSectionHeader>
<SubMenu
label={<FormattedMessage id="AdminPanel.OrganizationSettings" defaultMessage="Organization Settings" />}
if={isType(collective, ORGANIZATION) || isHost(collective)}
>
<MenuLink collective={collective} section={ABOUT_ORG_SECTIONS.INFO} />
<MenuLink collective={collective} section={ABOUT_ORG_SECTIONS.COLLECTIVE_PAGE} />
<MenuLink collective={collective} section={ABOUT_ORG_SECTIONS.CONNECTED_ACCOUNTS} />
<MenuLink collective={collective} section={ABOUT_ORG_SECTIONS.TEAM} />
<MenuLink collective={collective} section={ORG_BUDGET_SECTIONS.PAYMENT_METHODS} />
<MenuLink collective={collective} section={ORG_BUDGET_SECTIONS.PAYMENT_RECEIPTS} />
<MenuLink collective={collective} section={ORG_BUDGET_SECTIONS.TIERS} />
<MenuLink collective={collective} section={ORG_BUDGET_SECTIONS.GIFT_CARDS} />
<MenuLink
collective={collective}
section={ORG_BUDGET_SECTIONS.PENDING_ORDERS}
if={isType(collective, COLLECTIVE)}
/>
<MenuLink collective={collective} section={ALL_SECTIONS.WEBHOOKS} />
<MenuLink collective={collective} section={ALL_SECTIONS.ADVANCED} />
</SubMenu>
<SubMenu
label={<FormattedMessage id="AdminPanel.FiscalHostSettings" defaultMessage="Fiscal Host Settings" />}
if={isHost(collective) || (isType(collective, USER) && isHost(collective))}
>
<MenuLink collective={collective} section={FISCAL_HOST_SECTIONS.FISCAL_HOSTING} />
<MenuGroup if={isHost(collective)}>
<MenuLink collective={collective} section={FISCAL_HOST_SECTIONS.INVOICES_RECEIPTS} />
<MenuLink collective={collective} section={FISCAL_HOST_SECTIONS.RECEIVING_MONEY} />
<MenuLink collective={collective} section={FISCAL_HOST_SECTIONS.SENDING_MONEY} />
<MenuLink
collective={collective}
section={FISCAL_HOST_SECTIONS.HOST_VIRTUAL_CARDS}
if={hasFeature(collective, FEATURES.PRIVACY_VCC)}
/>
<MenuLink collective={collective} section={FISCAL_HOST_SECTIONS.HOST_TWO_FACTOR_AUTH} />
<MenuLink
collective={collective}
section={FISCAL_HOST_SECTIONS.POLICIES}
if={isOneOfTypes(collective, [USER, ORGANIZATION])}
/>
<MenuLink
collective={collective}
section={FISCAL_HOST_SECTIONS.HOST_PLAN}
if={isOneOfTypes(collective, [USER, ORGANIZATION])}
/>
<MenuLink
collective={collective}
section={FISCAL_HOST_SECTIONS.HOST_METRICS}
if={isOneOfTypes(collective, [USER, ORGANIZATION])}
/>
</MenuGroup>
</SubMenu>
</MenuGroup>

<MenuGroup if={!isHost(collective) && !isType(collective, ORGANIZATION)}>
<MenuSectionHeader>
{isType(collective, USER) ? (
<FormattedMessage id="AdminPanel.UserSettings" defaultMessage="User Settings" />
) : (
<FormattedMessage id="AdminPanel.CollectiveSettings" defaultMessage="Collective Settings" />
)}
</MenuSectionHeader>
<MenuLink collective={collective} section={COLLECTIVE_SECTIONS.INFO} />
<MenuLink collective={collective} section={COLLECTIVE_SECTIONS.COLLECTIVE_PAGE} />
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.COLLECTIVE_GOALS}
if={isType(collective, COLLECTIVE)}
/>
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.CONNECTED_ACCOUNTS}
if={isType(collective, COLLECTIVE)}
/>
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.POLICIES}
if={isOneOfTypes(collective, [COLLECTIVE, FUND])}
/>
<MenuLink collective={collective} section={COLLECTIVE_SECTIONS.EXPORT} if={isType(collective, COLLECTIVE)} />
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.HOST}
if={isOneOfTypes(collective, [COLLECTIVE, FUND])}
/>
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.TEAM}
if={isOneOfTypes(collective, [COLLECTIVE, FUND, ORGANIZATION, EVENT, PROJECT])}
/>
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.PAYMENT_METHODS}
if={isType(collective, USER)}
/>
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.PAYMENT_RECEIPTS}
if={isType(collective, USER)}
/>

<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.VIRTUAL_CARDS}
if={
isOneOfTypes(collective, [COLLECTIVE, FUND]) &&
hasFeature(collective.host, FEATURES.PRIVACY_VCC) &&
collective.virtualCards?.totalCount > 0
}
/>
<MenuLink collective={collective} section={COLLECTIVE_SECTIONS.TICKETS} if={isType(collective, EVENT)} />
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.TIERS}
if={isOneOfTypes(collective, [COLLECTIVE, FUND, EVENT, PROJECT])}
/>
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.WEBHOOKS}
if={isOneOfTypes(collective, [COLLECTIVE, USER, EVENT])}
/>
<MenuLink
collective={collective}
section={COLLECTIVE_SECTIONS.TWO_FACTOR_AUTH}
if={isType(collective, USER)}
/>
<MenuLink collective={collective} section={COLLECTIVE_SECTIONS.ADVANCED} />
</MenuGroup>
</React.Fragment>
);
}
};

Menu.propTypes = {
collective: PropTypes.shape({
slug: PropTypes.string,
name: PropTypes.string,
type: PropTypes.string,
isHost: PropTypes.bool,
}),
collectiveSlug: PropTypes.string,
};

export default Menu;