Skip to content

Commit 966131f

Browse files
kewitzgustavlrsn
andauthored
Collective Onboard: Dashboard Setup Guide (#11743)
* fix: dashboard NotificationBar position/margin * feat: add NotificationBar for unhosted and pending host application Collectives * feat: add Collective Welcome guide * refact: use DropdownMenuCheckboxItem * chore: update langs and schemas * fix: Spacing/margins on CollectiveOverview (harmonizing with other overviews) * refact: redirect to dashboard after creating collective * chore: update langs --------- Co-authored-by: Gustav Larsson <gustav.larsson@gmail.com>
1 parent b89f991 commit 966131f

File tree

35 files changed

+1567
-331
lines changed

35 files changed

+1567
-331
lines changed

components/NotificationBar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const getBackgroundColor = type => {
6969

7070
const NotificationBar = ({
7171
title,
72-
description,
72+
description = undefined,
7373
type,
7474
actions = undefined,
7575
inline = false,

components/dashboard/queries.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ export const adminPanelQuery = gql`
132132
... on AccountWithHost {
133133
hostFeePercent
134134
isApproved
135+
approvedAt
136+
hostApplication {
137+
id
138+
createdAt
139+
status
140+
}
135141
host {
136142
id
137143
requiredLegalDocuments

components/dashboard/sections/overview/CollectiveOverview.tsx

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from 'react';
2-
import { useQuery } from '@apollo/client';
3-
import { FlaskConical, X } from 'lucide-react';
2+
import { useMutation, useQuery } from '@apollo/client';
3+
import { FlaskConical, Settings, X } from 'lucide-react';
44
import { useRouter } from 'next/router';
55
import { FormattedMessage } from 'react-intl';
66
import { z } from 'zod';
77

88
import { HELP_MESSAGE } from '../../../../lib/constants/dismissable-help-message';
9+
import useLoggedInUser from '../../../../lib/hooks/useLoggedInUser';
910
import useQueryFilter from '../../../../lib/hooks/useQueryFilter';
1011
import { getDashboardRoute } from '../../../../lib/url-helpers';
1112

@@ -15,6 +16,12 @@ import Image from '../../../Image';
1516
import MessageBoxGraphqlError from '../../../MessageBoxGraphqlError';
1617
import { AlertDescription, AlertTitle } from '../../../ui/Alert';
1718
import { Button } from '../../../ui/Button';
19+
import {
20+
DropdownMenu,
21+
DropdownMenuCheckboxItem,
22+
DropdownMenuContent,
23+
DropdownMenuTrigger,
24+
} from '../../../ui/DropdownMenu';
1825
import { Popover, PopoverAnchor, PopoverContent } from '../../../ui/Popover';
1926
import { DashboardContext } from '../../DashboardContext';
2027
import DashboardHeader from '../../DashboardHeader';
@@ -28,9 +35,10 @@ import AccountTable from './AccountTable';
2835
import { ConvertedAccountMessage } from './ConvertedAccountMessage';
2936
import type { MetricProps } from './Metric';
3037
import { Metric } from './Metric';
31-
import { overviewMetricsQuery } from './queries';
38+
import { editAccountSettingMutation, overviewMetricsQuery } from './queries';
3239
import { Timeline } from './Timeline';
3340
import { AccountTodoList } from './TodoList';
41+
import { WelcomeCollective } from './Welcome';
3442

3543
export const schema = z.object({
3644
period: periodCompareFilter.schema,
@@ -41,8 +49,28 @@ export const schema = z.object({
4149

4250
export function CollectiveOverview({ accountSlug }: DashboardSectionProps) {
4351
const { account } = React.useContext(DashboardContext);
52+
const { LoggedInUser, refetchLoggedInUser } = useLoggedInUser();
4453
const [showFeedbackModal, setShowFeedbackModal] = React.useState(false);
54+
const [showSetupGuide, setShowSetupGuide] = React.useState(undefined);
55+
const [editAccountSetting] = useMutation(editAccountSettingMutation);
4556
const router = useRouter();
57+
58+
const handleSetupGuideToggle = React.useCallback(
59+
async (open: boolean) => {
60+
setShowSetupGuide(open);
61+
62+
await editAccountSetting({
63+
variables: {
64+
account: { legacyId: LoggedInUser.collective.id },
65+
key: `showSetupGuide.id${account.legacyId}`,
66+
value: open,
67+
},
68+
}).catch(() => {});
69+
await refetchLoggedInUser();
70+
},
71+
[account, LoggedInUser, editAccountSetting, refetchLoggedInUser],
72+
);
73+
4674
const queryFilter = useQueryFilter({
4775
schema,
4876
toVariables: {
@@ -176,11 +204,11 @@ export function CollectiveOverview({ accountSlug }: DashboardSectionProps) {
176204

177205
return (
178206
<div className="space-y-6">
179-
<div className="flex flex-col gap-3">
180-
<DashboardHeader
181-
title={<FormattedMessage id="AdminPanel.Menu.Overview" defaultMessage="Overview" />}
182-
titleRoute={getDashboardRoute(account, 'overview')}
183-
actions={
207+
<DashboardHeader
208+
title={<FormattedMessage id="AdminPanel.Menu.Overview" defaultMessage="Overview" />}
209+
titleRoute={getDashboardRoute(account, 'overview')}
210+
actions={
211+
<div className="flex gap-2">
184212
<Popover open>
185213
<PopoverAnchor>
186214
<Button size="sm" variant="outline" className="gap-1.5" onClick={() => setShowFeedbackModal(true)}>
@@ -234,11 +262,28 @@ export function CollectiveOverview({ accountSlug }: DashboardSectionProps) {
234262
)}
235263
</DismissibleMessage>
236264
</Popover>
237-
}
238-
/>
239-
<ConvertedAccountMessage account={account} />
265+
<DropdownMenu>
266+
<DropdownMenuTrigger asChild>
267+
<Button size="icon-sm" variant="outline">
268+
<Settings size={16} />
269+
</Button>
270+
</DropdownMenuTrigger>
271+
<DropdownMenuContent align="end">
272+
<DropdownMenuCheckboxItem
273+
checked={showSetupGuide}
274+
onClick={() => handleSetupGuideToggle(!showSetupGuide)}
275+
>
276+
<FormattedMessage defaultMessage="Display setup guide" id="SetupGuide.DisplaySetupGuide" />
277+
</DropdownMenuCheckboxItem>
278+
</DropdownMenuContent>
279+
</DropdownMenu>
280+
</div>
281+
}
282+
/>
283+
<ConvertedAccountMessage account={account} />
284+
<WelcomeCollective account={account} open={showSetupGuide} setOpen={handleSetupGuideToggle} />
285+
<div className="space-y-3">
240286
<Filterbar hideSeparator {...queryFilter} />
241-
242287
<div className="grid grid-flow-dense grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-1 lg:grid-cols-3">
243288
{metrics
244289
.filter(metric => !metric.hide)

components/dashboard/sections/overview/IndividualOverview.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import { FEEDBACK_KEY, FeedbackModal } from '../../../FeedbackModal';
1212
import Image from '../../../Image';
1313
import { Alert, AlertDescription, AlertTitle } from '../../../ui/Alert';
1414
import { Button } from '../../../ui/Button';
15-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../../ui/DropdownMenu';
15+
import {
16+
DropdownMenu,
17+
DropdownMenuCheckboxItem,
18+
DropdownMenuContent,
19+
DropdownMenuTrigger,
20+
} from '../../../ui/DropdownMenu';
1621
import DashboardHeader from '../../DashboardHeader';
1722
import type { DashboardSectionProps } from '../../types';
1823

@@ -73,13 +78,12 @@ const Home = ({ accountSlug }: DashboardSectionProps) => {
7378
</Button>
7479
</DropdownMenuTrigger>
7580
<DropdownMenuContent align="end">
76-
<DropdownMenuItem onClick={() => handleSetupGuideToggle(!showWelcomeGuide)}>
77-
{showWelcomeGuide ? (
78-
<FormattedMessage defaultMessage="Hide welcome guide" id="SetupGuide.HideWelcome" />
79-
) : (
80-
<FormattedMessage defaultMessage="Show welcome guide" id="SetupGuide.ShowWelcome" />
81-
)}
82-
</DropdownMenuItem>
81+
<DropdownMenuCheckboxItem
82+
checked={showWelcomeGuide}
83+
onClick={() => handleSetupGuideToggle(!showWelcomeGuide)}
84+
>
85+
<FormattedMessage defaultMessage="Display welcome guide" id="SetupGuide.DisplayWelcomeGuide" />
86+
</DropdownMenuCheckboxItem>
8387
</DropdownMenuContent>
8488
</DropdownMenu>
8589
}

components/dashboard/sections/overview/OrgOverview.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { hasAccountMoneyManagement } from '@/lib/collective';
77
import useLoggedInUser from '@/lib/hooks/useLoggedInUser';
88

99
import { Collapsible, CollapsibleContent } from '@/components/ui/Collapsible';
10-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/DropdownMenu';
10+
import {
11+
DropdownMenu,
12+
DropdownMenuCheckboxItem,
13+
DropdownMenuContent,
14+
DropdownMenuTrigger,
15+
} from '@/components/ui/DropdownMenu';
1116

1217
import { Button } from '../../../ui/Button';
1318
import { DashboardContext } from '../../DashboardContext';
@@ -87,13 +92,12 @@ export function OrgOverview() {
8792
</Button>
8893
</DropdownMenuTrigger>
8994
<DropdownMenuContent align="end">
90-
<DropdownMenuItem onClick={() => handleSetupGuideToggle(!showSetupGuide)}>
91-
{showSetupGuide ? (
92-
<FormattedMessage defaultMessage="Hide setup guide" id="SetupGuide.HideSetupGuide" />
93-
) : (
94-
<FormattedMessage defaultMessage="Show setup guide" id="SetupGuide.ShowSetupGuide" />
95-
)}
96-
</DropdownMenuItem>
95+
<DropdownMenuCheckboxItem
96+
checked={showSetupGuide}
97+
onClick={() => handleSetupGuideToggle(!showSetupGuide)}
98+
>
99+
<FormattedMessage defaultMessage="Display setup guide" id="SetupGuide.DisplaySetupGuide" />
100+
</DropdownMenuCheckboxItem>
97101
</DropdownMenuContent>
98102
</DropdownMenu>
99103
}

components/dashboard/sections/overview/Welcome.tsx

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { WelcomeOrganizationQuery } from '@/lib/graphql/types/v2/graphql';
88
import useLoggedInUser from '@/lib/hooks/useLoggedInUser';
99
import { cn } from '@/lib/utils';
1010
import type { Category, Step } from '@/lib/welcome';
11-
import { INDIVIDUAL_CATEGORIES, ORG_CATEGORIES, sortSteps } from '@/lib/welcome';
11+
import { COLLECTIVE_CATEGORIES, INDIVIDUAL_CATEGORIES, ORGANIZATION_CATEGORIES, sortSteps } from '@/lib/welcome';
1212

1313
import { AccountingCategorySelectFieldsFragment } from '@/components/AccountingCategorySelect';
1414
import { Drawer } from '@/components/Drawer';
@@ -29,6 +29,7 @@ const welcomeOrganizationQuery = gql`
2929
slug
3030
description
3131
currency
32+
type
3233
legacyId
3334
longDescription
3435
isHost
@@ -58,6 +59,27 @@ const welcomeOrganizationQuery = gql`
5859
id
5960
type
6061
}
62+
updates(limit: 1) {
63+
nodes {
64+
id
65+
}
66+
}
67+
projects: childrenAccounts(accountType: [PROJECT], limit: 1) {
68+
nodes {
69+
id
70+
}
71+
}
72+
approvedExpenses: expenses(status: APPROVED, direction: RECEIVED, limit: 1) {
73+
nodes {
74+
id
75+
}
76+
}
77+
policies {
78+
EXPENSE_POLICIES {
79+
invoicePolicy
80+
receiptPolicy
81+
}
82+
}
6183
... on AccountWithPlatformSubscription {
6284
platformSubscription {
6385
isCurrent
@@ -71,6 +93,21 @@ const welcomeOrganizationQuery = gql`
7193
}
7294
... on AccountWithContributions {
7395
contributionPolicy
96+
tiers(limit: 1) {
97+
nodes {
98+
id
99+
}
100+
}
101+
}
102+
... on AccountWithHost {
103+
hostFeePercent
104+
isApproved
105+
approvedAt
106+
hostApplication {
107+
id
108+
createdAt
109+
status
110+
}
74111
}
75112
... on Organization {
76113
hasHosting
@@ -323,7 +360,76 @@ export const WelcomeOrganization = ({ account: _account, setOpen, open }) => {
323360
</CardAction>
324361
</CardHeader>
325362
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
326-
{Object.entries(ORG_CATEGORIES).map(([key, category]) => (
363+
{Object.entries(ORGANIZATION_CATEGORIES).map(([key, category]) => (
364+
<WelcomeCategoryButton
365+
key={key}
366+
{...category}
367+
account={data?.account}
368+
onClick={() => setExpandedCategory(key)}
369+
/>
370+
))}
371+
</CardContent>
372+
<WelcomeDrawer
373+
category={ORGANIZATION_CATEGORIES[expandedCategory]}
374+
account={data?.account}
375+
onClose={() => setExpandedCategory(null)}
376+
open={expandedCategory !== null}
377+
/>
378+
</Card>
379+
</CollapsibleContent>
380+
</Collapsible>
381+
);
382+
};
383+
384+
export const WelcomeCollective = ({ account: _account, setOpen, open }) => {
385+
const intl = useIntl();
386+
const { data } = useQuery<WelcomeOrganizationQuery>(welcomeOrganizationQuery, {
387+
variables: { accountSlug: _account?.slug },
388+
skip: !_account,
389+
390+
fetchPolicy: 'cache-and-network',
391+
});
392+
const { LoggedInUser } = useLoggedInUser();
393+
// Undefined here means the initial state, after that we can set to null or a specific category ID
394+
const [expandedCategory, setExpandedCategory] = useState<null | string>(null);
395+
396+
useEffect(() => {
397+
if (LoggedInUser && open === undefined && data?.account) {
398+
const showGuide = LoggedInUser?.shouldDisplaySetupGuide(data.account);
399+
setOpen(showGuide !== false ? true : false);
400+
}
401+
}, [data?.account, open, setOpen, LoggedInUser]);
402+
403+
return (
404+
<Collapsible open={open} onOpenChange={setOpen}>
405+
<CollapsibleContent>
406+
<Card>
407+
<CardHeader>
408+
<CardTitle className="text-xl">
409+
<FormattedMessage
410+
defaultMessage="What would like to do with the platform?"
411+
id="Welcome.Organization.Title"
412+
/>
413+
</CardTitle>
414+
<CardDescription>
415+
<FormattedMessage
416+
defaultMessage="Get started with the basics or set up additional functionalities."
417+
id="Welcome.Collective.Description"
418+
/>
419+
</CardDescription>
420+
<CardAction>
421+
<Button
422+
variant="ghost"
423+
size="icon-xs"
424+
onClick={() => setOpen(false)}
425+
aria-label={intl.formatMessage({ defaultMessage: 'Hide setup guide', id: 'SetupGuide.HideSetupGuide' })}
426+
>
427+
<X className="h-4 w-4" />
428+
</Button>
429+
</CardAction>
430+
</CardHeader>
431+
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
432+
{Object.entries(COLLECTIVE_CATEGORIES).map(([key, category]) => (
327433
<WelcomeCategoryButton
328434
key={key}
329435
{...category}
@@ -333,7 +439,7 @@ export const WelcomeOrganization = ({ account: _account, setOpen, open }) => {
333439
))}
334440
</CardContent>
335441
<WelcomeDrawer
336-
category={ORG_CATEGORIES[expandedCategory]}
442+
category={COLLECTIVE_CATEGORIES[expandedCategory]}
337443
account={data?.account}
338444
onClose={() => setExpandedCategory(null)}
339445
open={expandedCategory !== null}

components/signup/common.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,7 @@ export function InviteAdminForm({ nextStep, createdAccount }: SignupStepProps) {
208208
onClick={() => nextStep()}
209209
data-cy="skip-button"
210210
>
211-
{createdAccount?.type === 'COLLECTIVE' ? (
212-
<FormattedMessage defaultMessage="Skip to my Profile" id="SkipToProfile" />
213-
) : (
214-
<FormattedMessage defaultMessage="Skip to Dashboard" id="SkipToDashboard" />
215-
)}
211+
<FormattedMessage defaultMessage="Skip to Dashboard" id="SkipToDashboard" />
216212
</Button>
217213
<Button
218214
type="submit"

0 commit comments

Comments
 (0)