chore: Menu templates (#1644)

* chore: Menu template system

* NewTemplateMenu

* UserMenu

* MenuItemsTemplate -> DropdownMenuItems

* support nested menus

* DocumentMenu

* BreadcrumbMenu

* isInvited
This commit is contained in:
Tom Moor
2020-11-14 20:44:31 -08:00
committed by GitHub
parent 19ab32f551
commit 12a2e1c387
16 changed files with 440 additions and 300 deletions

View File

@ -1,25 +1,22 @@
// @flow // @flow
import * as React from "react"; import * as React from "react";
import { Link } from "react-router-dom"; import { DropdownMenu } from "components/DropdownMenu";
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
type Props = { type Props = {
label: React.Node, label: React.Node,
path: Array<any>, path: Array<any>,
}; };
export default class BreadcrumbMenu extends React.Component<Props> { export default function BreadcrumbMenu({ label, path }: Props) {
render() {
const { path } = this.props;
return ( return (
<DropdownMenu label={this.props.label} position="center"> <DropdownMenu label={label} position="center">
{path.map((item) => ( <DropdownMenuItems
<DropdownMenuItem as={Link} to={item.url} key={item.id}> items={path.map((item) => ({
{item.title} title: item.title,
</DropdownMenuItem> to: item.url,
))} }))}
/>
</DropdownMenu> </DropdownMenu>
); );
} }
}

View File

@ -18,7 +18,7 @@ type Children =
| React.Node | React.Node
| ((options: { closePortal: () => void }) => React.Node); | ((options: { closePortal: () => void }) => React.Node);
type Props = { type Props = {|
label?: React.Node, label?: React.Node,
onOpen?: () => void, onOpen?: () => void,
onClose?: () => void, onClose?: () => void,
@ -27,7 +27,7 @@ type Props = {
hover?: boolean, hover?: boolean,
style?: Object, style?: Object,
position?: "left" | "right" | "center", position?: "left" | "right" | "center",
}; |};
@observer @observer
class DropdownMenu extends React.Component<Props> { class DropdownMenu extends React.Component<Props> {

View File

@ -0,0 +1,128 @@
// @flow
import * as React from "react";
import { Link } from "react-router-dom";
import DropdownMenu from "./DropdownMenu";
import DropdownMenuItem from "./DropdownMenuItem";
type MenuItem =
| {|
title: React.Node,
to: string,
visible?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
onClick: (event: SyntheticEvent<>) => void | Promise<void>,
visible?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
href: string,
visible?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
visible?: boolean,
disabled?: boolean,
style?: Object,
hover?: boolean,
items: MenuItem[],
|}
| {|
type: "separator",
visible?: boolean,
|}
| {|
type: "heading",
visible?: boolean,
title: React.Node,
|};
type Props = {|
items: MenuItem[],
|};
export default function DropdownMenuItems({ items }: Props): React.Node {
let filtered = items.filter((item) => item.visible !== false);
// this block literally just trims unneccessary separators
filtered = filtered.reduce((acc, item, index) => {
// trim separators from start / end
if (item.type === "separator" && index === 0) return acc;
if (item.type === "separator" && index === filtered.length - 1) return acc;
// trim double separators looking ahead / behind
const prev = filtered[index - 1];
if (prev && prev.type === "separator" && item.type === "separator")
return acc;
// otherwise, continue
return [...acc, item];
}, []);
return filtered.map((item, index) => {
if (item.to) {
return (
<DropdownMenuItem
as={Link}
to={item.to}
key={index}
disabled={item.disabled}
>
{item.title}
</DropdownMenuItem>
);
}
if (item.href) {
return (
<DropdownMenuItem
href={item.href}
key={index}
disabled={item.disabled}
target="_blank"
>
{item.title}
</DropdownMenuItem>
);
}
if (item.onClick) {
return (
<DropdownMenuItem
onClick={item.onClick}
disabled={item.disabled}
key={index}
>
{item.title}
</DropdownMenuItem>
);
}
if (item.items) {
return (
<DropdownMenu
style={item.style}
label={
<DropdownMenuItem disabled={item.disabled}>
{item.title}
</DropdownMenuItem>
}
hover={item.hover}
key={index}
>
<DropdownMenuItems items={item.items} />
</DropdownMenu>
);
}
if (item.type === "separator") {
return <hr key={index} />;
}
return null;
});
}

View File

@ -11,7 +11,8 @@ import CollectionDelete from "scenes/CollectionDelete";
import CollectionEdit from "scenes/CollectionEdit"; import CollectionEdit from "scenes/CollectionEdit";
import CollectionExport from "scenes/CollectionExport"; import CollectionExport from "scenes/CollectionExport";
import CollectionMembers from "scenes/CollectionMembers"; import CollectionMembers from "scenes/CollectionMembers";
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; import { DropdownMenu } from "components/DropdownMenu";
import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
import Modal from "components/Modal"; import Modal from "components/Modal";
import VisuallyHidden from "components/VisuallyHidden"; import VisuallyHidden from "components/VisuallyHidden";
import getDataTransferFiles from "utils/getDataTransferFiles"; import getDataTransferFiles from "utils/getDataTransferFiles";
@ -139,41 +140,43 @@ class CollectionMenu extends React.Component<Props> {
/> />
</Modal> </Modal>
<DropdownMenu onOpen={onOpen} onClose={onClose} position={position}> <DropdownMenu onOpen={onOpen} onClose={onClose} position={position}>
{collection && ( <DropdownMenuItems
<> items={[
{can.update && ( {
<DropdownMenuItem onClick={this.onNewDocument}> title: "New document",
New document visible: !!(collection && can.update),
</DropdownMenuItem> onClick: this.onNewDocument,
)} },
{can.update && ( {
<DropdownMenuItem onClick={this.onImportDocument}> title: "Import document",
Import document visible: !!(collection && can.update),
</DropdownMenuItem> onClick: this.onImportDocument,
)} },
{can.update && <hr />} {
{can.update && ( type: "separator",
<DropdownMenuItem onClick={this.handleEditCollectionOpen}> },
Edit {
</DropdownMenuItem> title: "Edit…",
)} visible: !!(collection && can.update),
{can.update && ( onClick: this.handleEditCollectionOpen,
<DropdownMenuItem onClick={this.handleMembersModalOpen}> },
Permissions {
</DropdownMenuItem> title: "Permissions…",
)} visible: !!(collection && can.update),
{can.export && ( onClick: this.handleMembersModalOpen,
<DropdownMenuItem onClick={this.handleExportCollectionOpen}> },
Export {
</DropdownMenuItem> title: "Export…",
)} visible: !!(collection && can.export),
</> onClick: this.handleExportCollectionOpen,
)} },
{can.delete && ( {
<DropdownMenuItem onClick={this.handleDeleteCollectionOpen}> title: "Delete…",
Delete visible: !!(collection && can.delete),
</DropdownMenuItem> onClick: this.handleDeleteCollectionOpen,
)} },
]}
/>
</DropdownMenu> </DropdownMenu>
<Modal <Modal
title="Edit collection" title="Edit collection"

View File

@ -12,11 +12,8 @@ import DocumentDelete from "scenes/DocumentDelete";
import DocumentShare from "scenes/DocumentShare"; import DocumentShare from "scenes/DocumentShare";
import DocumentTemplatize from "scenes/DocumentTemplatize"; import DocumentTemplatize from "scenes/DocumentTemplatize";
import CollectionIcon from "components/CollectionIcon"; import CollectionIcon from "components/CollectionIcon";
import { import { DropdownMenu } from "components/DropdownMenu";
DropdownMenu, import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
DropdownMenuItem,
Header,
} from "components/DropdownMenu";
import Modal from "components/Modal"; import Modal from "components/Modal";
import { import {
documentHistoryUrl, documentHistoryUrl,
@ -170,7 +167,7 @@ class DocumentMenu extends React.Component<Props> {
} = this.props; } = this.props;
const can = policies.abilities(document.id); const can = policies.abilities(document.id);
const canShareDocuments = can.share && auth.team && auth.team.sharing; const canShareDocuments = !!(can.share && auth.team && auth.team.sharing);
const canViewHistory = can.read && !can.restore; const canViewHistory = can.read && !can.restore;
const collection = collections.get(document.collectionId); const collection = collections.get(document.collectionId);
@ -183,146 +180,147 @@ class DocumentMenu extends React.Component<Props> {
onClose={onClose} onClose={onClose}
label={label} label={label}
> >
{can.unarchive && ( <DropdownMenuItems
<DropdownMenuItem onClick={this.handleRestore}> items={[
Restore {
</DropdownMenuItem> title: "Restore",
)} visible: !!can.unarchive,
{can.restore && onClick: this.handleRestore,
(collection ? ( },
<DropdownMenuItem onClick={this.handleRestore}> {
Restore title: "Restore",
</DropdownMenuItem> visible: !!(collection && can.restore),
) : ( onClick: this.handleRestore,
<DropdownMenu },
label={<DropdownMenuItem>Restore</DropdownMenuItem>} {
style={{ title: "Restore…",
visible: !collection && !!can.restore,
style: {
left: -170, left: -170,
position: "relative", position: "relative",
top: -40, top: -40,
}} },
hover hover: true,
> items: [
<Header>Choose a collection</Header> {
{collections.orderedData.map((collection) => { type: "heading",
title: "Choose a collection",
},
...collections.orderedData.map((collection) => {
const can = policies.abilities(collection.id); const can = policies.abilities(collection.id);
return ( return {
<DropdownMenuItem title: (
key={collection.id} <>
onClick={(ev) =>
this.handleRestore(ev, { collectionId: collection.id })
}
disabled={!can.update}
>
<CollectionIcon collection={collection} /> <CollectionIcon collection={collection} />
&nbsp;{collection.name} &nbsp;{collection.name}
</DropdownMenuItem>
);
})}
</DropdownMenu>
))}
{showPin &&
(document.pinned
? can.unpin && (
<DropdownMenuItem onClick={this.handleUnpin}>
Unpin
</DropdownMenuItem>
)
: can.pin && (
<DropdownMenuItem onClick={this.handlePin}>
Pin to collection
</DropdownMenuItem>
))}
{document.isStarred
? can.unstar && (
<DropdownMenuItem onClick={this.handleUnstar}>
Unstar
</DropdownMenuItem>
)
: can.star && (
<DropdownMenuItem onClick={this.handleStar}>
Star
</DropdownMenuItem>
)}
{canShareDocuments && (
<DropdownMenuItem
onClick={this.handleShareLink}
title="Create a public share link"
>
Share link
</DropdownMenuItem>
)}
{showToggleEmbeds && (
<>
{document.embedsDisabled ? (
<DropdownMenuItem onClick={document.enableEmbeds}>
Enable embeds
</DropdownMenuItem>
) : (
<DropdownMenuItem onClick={document.disableEmbeds}>
Disable embeds
</DropdownMenuItem>
)}
</> </>
)} ),
{!can.restore && <hr />} onClick: (ev) =>
this.handleRestore(ev, { collectionId: collection.id }),
{can.createChildDocument && ( disabled: !can.update,
<DropdownMenuItem };
onClick={this.handleNewChild} }),
title="Create a nested document inside the current document" ],
> },
New nested document {
</DropdownMenuItem> title: "Unpin",
)} onClick: this.handleUnpin,
{can.update && !document.isTemplate && ( visible: !!(showPin && document.pinned && can.unpin),
<DropdownMenuItem onClick={this.handleOpenTemplateModal}> },
Create template {
</DropdownMenuItem> title: "Pin to collection",
)} onClick: this.handlePin,
{can.unpublish && ( visible: !!(showPin && !document.pinned && can.pin),
<DropdownMenuItem onClick={this.handleUnpublish}> },
Unpublish {
</DropdownMenuItem> title: "Unstar",
)} onClick: this.handleUnstar,
{can.update && ( visible: document.isStarred && !!can.unstar,
<DropdownMenuItem onClick={this.handleEdit}>Edit</DropdownMenuItem> },
)} {
{can.update && ( title: "Star",
<DropdownMenuItem onClick={this.handleDuplicate}> onClick: this.handleStar,
Duplicate visible: !document.isStarred && !!can.star,
</DropdownMenuItem> },
)} {
{can.archive && ( title: "Share link…",
<DropdownMenuItem onClick={this.handleArchive}> onClick: this.handleShareLink,
Archive visible: canShareDocuments,
</DropdownMenuItem> },
)} {
{can.delete && ( title: "Enable embeds",
<DropdownMenuItem onClick={this.handleDelete}> onClick: document.enableEmbeds,
Delete visible: !!showToggleEmbeds && document.embedsDisabled,
</DropdownMenuItem> },
)} {
{can.move && ( title: "Disable embeds",
<DropdownMenuItem onClick={this.handleMove}>Move</DropdownMenuItem> onClick: document.disableEmbeds,
)} visible: !!showToggleEmbeds && !document.embedsDisabled,
<hr /> },
{canViewHistory && ( {
<> type: "separator",
<DropdownMenuItem onClick={this.handleDocumentHistory}> },
History {
</DropdownMenuItem> title: "New nested document",
</> onClick: this.handleNewChild,
)} visible: !!can.createChildDocument,
{can.download && ( },
<DropdownMenuItem onClick={this.handleExport}> {
Download title: "Create template…",
</DropdownMenuItem> onClick: this.handleOpenTemplateModal,
)} visible: !!can.update && !document.isTemplate,
{showPrint && ( },
<DropdownMenuItem onClick={window.print}>Print</DropdownMenuItem> {
)} title: "Edit",
onClick: this.handleEdit,
visible: !!can.update,
},
{
title: "Duplicate",
onClick: this.handleDuplicate,
visible: !!can.update,
},
{
title: "Unpublish",
onClick: this.handleUnpublish,
visible: !!can.unpublish,
},
{
title: "Archive",
onClick: this.handleArchive,
visible: !!can.archive,
},
{
title: "Delete…",
onClick: this.handleDelete,
visible: !!can.delete,
},
{
title: "Move…",
onClick: this.handleMove,
visible: !!can.move,
},
{
type: "separator",
},
{
title: "History",
onClick: this.handleDocumentHistory,
visible: canViewHistory,
},
{
title: "Download",
onClick: this.handleExport,
visible: !!can.download,
},
{
title: "Print",
onClick: window.print,
visible: !!showPrint,
},
]}
/>
</DropdownMenu> </DropdownMenu>
<Modal <Modal
title={`Delete ${this.props.document.noun}`} title={`Delete ${this.props.document.noun}`}

View File

@ -8,8 +8,8 @@ import UiStore from "stores/UiStore";
import Group from "models/Group"; import Group from "models/Group";
import GroupDelete from "scenes/GroupDelete"; import GroupDelete from "scenes/GroupDelete";
import GroupEdit from "scenes/GroupEdit"; import GroupEdit from "scenes/GroupEdit";
import { DropdownMenu } from "components/DropdownMenu";
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
import Modal from "components/Modal"; import Modal from "components/Modal";
type Props = { type Props = {
@ -72,27 +72,29 @@ class GroupMenu extends React.Component<Props> {
onSubmit={this.handleDeleteModalClose} onSubmit={this.handleDeleteModalClose}
/> />
</Modal> </Modal>
<DropdownMenu onOpen={onOpen} onClose={onClose}> <DropdownMenu onOpen={onOpen} onClose={onClose}>
{group && ( <DropdownMenuItems
<> items={[
<DropdownMenuItem onClick={this.props.onMembers}> {
Members title: "Members…",
</DropdownMenuItem> onClick: this.props.onMembers,
visible: !!(group && can.read),
{(can.update || can.delete) && <hr />} },
{
{can.update && ( type: "separator",
<DropdownMenuItem onClick={this.onEdit}>Edit</DropdownMenuItem> },
)} {
title: "Edit…",
{can.delete && ( onClick: this.onEdit,
<DropdownMenuItem onClick={this.onDelete}> visible: !!(group && can.update),
Delete },
</DropdownMenuItem> {
)} title: "Delete…",
</> onClick: this.onDelete,
)} visible: !!(group && can.delete),
},
]}
/>
</DropdownMenu> </DropdownMenu>
</> </>
); );

View File

@ -1,13 +1,13 @@
// @flow // @flow
import { observable } from "mobx"; import { observable } from "mobx";
import { observer, inject } from "mobx-react"; import { observer, inject } from "mobx-react";
import { MoreIcon } from "outline-icons";
import * as React from "react"; import * as React from "react";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
import CollectionsStore from "stores/CollectionsStore"; import CollectionsStore from "stores/CollectionsStore";
import Document from "models/Document"; import Document from "models/Document";
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; import { DropdownMenu } from "components/DropdownMenu";
import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
import { newDocumentUrl } from "utils/routeHelpers"; import { newDocumentUrl } from "utils/routeHelpers";
type Props = { type Props = {
@ -39,20 +39,28 @@ class NewChildDocumentMenu extends React.Component<Props> {
render() { render() {
if (this.redirectTo) return <Redirect to={this.redirectTo} push />; if (this.redirectTo) return <Redirect to={this.redirectTo} push />;
const { label, document, collections, ...rest } = this.props; const { label, document, collections } = this.props;
const collection = collections.get(document.collectionId); const collection = collections.get(document.collectionId);
return ( return (
<DropdownMenu label={label || <MoreIcon />} {...rest}> <DropdownMenu label={label}>
<DropdownMenuItem onClick={this.handleNewDocument}> <DropdownMenuItems
items={[
{
title: (
<span> <span>
New document in{" "} New document in{" "}
<strong>{collection ? collection.name : "collection"}</strong> <strong>{collection ? collection.name : "collection"}</strong>
</span> </span>
</DropdownMenuItem> ),
<DropdownMenuItem onClick={this.handleNewChild}> onClick: this.handleNewDocument,
New nested document },
</DropdownMenuItem> {
title: "New nested document",
onClick: this.handleNewChild,
},
]}
/>
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -10,11 +10,8 @@ import DocumentsStore from "stores/DocumentsStore";
import PoliciesStore from "stores/PoliciesStore"; import PoliciesStore from "stores/PoliciesStore";
import Button from "components/Button"; import Button from "components/Button";
import CollectionIcon from "components/CollectionIcon"; import CollectionIcon from "components/CollectionIcon";
import { import { DropdownMenu, Header } from "components/DropdownMenu";
DropdownMenu, import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
DropdownMenuItem,
Header,
} from "components/DropdownMenu";
import { newDocumentUrl } from "utils/routeHelpers"; import { newDocumentUrl } from "utils/routeHelpers";
type Props = { type Props = {
@ -63,20 +60,18 @@ class NewDocumentMenu extends React.Component<Props> {
{...rest} {...rest}
> >
<Header>Choose a collection</Header> <Header>Choose a collection</Header>
{collections.orderedData.map((collection) => { <DropdownMenuItems
const can = policies.abilities(collection.id); items={collections.orderedData.map((collection) => ({
onClick: () => this.handleNewDocument(collection.id),
return ( disabled: !policies.abilities(collection.id).update,
<DropdownMenuItem title: (
key={collection.id} <>
onClick={() => this.handleNewDocument(collection.id)}
disabled={!can.update}
>
<CollectionIcon collection={collection} /> <CollectionIcon collection={collection} />
&nbsp;{collection.name} &nbsp;{collection.name}
</DropdownMenuItem> </>
); ),
})} }))}
/>
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -9,11 +9,8 @@ import CollectionsStore from "stores/CollectionsStore";
import PoliciesStore from "stores/PoliciesStore"; import PoliciesStore from "stores/PoliciesStore";
import Button from "components/Button"; import Button from "components/Button";
import CollectionIcon from "components/CollectionIcon"; import CollectionIcon from "components/CollectionIcon";
import { import { DropdownMenu, Header } from "components/DropdownMenu";
DropdownMenu, import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
DropdownMenuItem,
Header,
} from "components/DropdownMenu";
import { newDocumentUrl } from "utils/routeHelpers"; import { newDocumentUrl } from "utils/routeHelpers";
type Props = { type Props = {
@ -53,20 +50,18 @@ class NewTemplateMenu extends React.Component<Props> {
{...rest} {...rest}
> >
<Header>Choose a collection</Header> <Header>Choose a collection</Header>
{collections.orderedData.map((collection) => { <DropdownMenuItems
const can = policies.abilities(collection.id); items={collections.orderedData.map((collection) => ({
onClick: () => this.handleNewDocument(collection.id),
return ( disabled: !policies.abilities(collection.id).update,
<DropdownMenuItem title: (
key={collection.id} <>
onClick={() => this.handleNewDocument(collection.id)}
disabled={!can.update}
>
<CollectionIcon collection={collection} /> <CollectionIcon collection={collection} />
&nbsp;{collection.name} &nbsp;{collection.name}
</DropdownMenuItem> </>
); ),
})} }))}
/>
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -4,7 +4,8 @@ import * as React from "react";
import UsersStore from "stores/UsersStore"; import UsersStore from "stores/UsersStore";
import User from "models/User"; import User from "models/User";
import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; import { DropdownMenu } from "components/DropdownMenu";
import DropdownMenuItems from "components/DropdownMenu/DropdownMenuItems";
type Props = { type Props = {
user: User, user: User,
@ -65,31 +66,38 @@ class UserMenu extends React.Component<Props> {
return ( return (
<DropdownMenu> <DropdownMenu>
{user.isAdmin && ( <DropdownMenuItems
<DropdownMenuItem onClick={this.handleDemote}> items={[
Make {user.name} a member {
</DropdownMenuItem> title: `Make ${user.name} a member…`,
)} onClick: this.handleDemote,
{!user.isAdmin && !user.isSuspended && ( visible: user.isAdmin,
<DropdownMenuItem onClick={this.handlePromote}> },
Make {user.name} an admin {
</DropdownMenuItem> title: `Make ${user.name} an admin…`,
)} onClick: this.handlePromote,
{!user.lastActiveAt && ( visible: !user.isAdmin && !user.isSuspended,
<DropdownMenuItem onClick={this.handleRevoke}> },
Revoke invite {
</DropdownMenuItem> type: "separator",
)} },
{user.lastActiveAt && {
(user.isSuspended ? ( title: "Revoke invite…",
<DropdownMenuItem onClick={this.handleActivate}> onClick: this.handleRevoke,
Activate account visible: user.isInvited,
</DropdownMenuItem> },
) : ( {
<DropdownMenuItem onClick={this.handleSuspend}> title: "Reactivate account",
Suspend account onClick: this.handleActivate,
</DropdownMenuItem> visible: !user.isInvited && user.isSuspended,
))} },
{
title: "Suspend account",
onClick: this.handleSuspend,
visible: !user.isInvited && !user.isSuspended,
},
]}
/>
</DropdownMenu> </DropdownMenu>
); );
} }

View File

@ -1,4 +1,5 @@
// @flow // @flow
import { computed } from "mobx";
import BaseModel from "./BaseModel"; import BaseModel from "./BaseModel";
class User extends BaseModel { class User extends BaseModel {
@ -10,6 +11,11 @@ class User extends BaseModel {
lastActiveAt: string; lastActiveAt: string;
isSuspended: boolean; isSuspended: boolean;
createdAt: string; createdAt: string;
@computed
get isInvited(): boolean {
return !this.lastActiveAt;
}
} }
export default User; export default User;

View File

@ -45,7 +45,7 @@ const MemberListItem = ({
) : ( ) : (
"Never signed in" "Never signed in"
)} )}
{!user.lastActiveAt && <Badge>Invited</Badge>} {user.isInvited && <Badge>Invited</Badge>}
{user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>} {user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>}
</> </>
} }

View File

@ -28,7 +28,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
) : ( ) : (
"Never signed in" "Never signed in"
)} )}
{!user.lastActiveAt && <Badge>Invited</Badge>} {user.isInvited && <Badge>Invited</Badge>}
{user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>} {user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>}
</> </>
} }

View File

@ -35,7 +35,7 @@ const GroupMemberListItem = ({
) : ( ) : (
"Never signed in" "Never signed in"
)} )}
{!user.lastActiveAt && <Badge>Invited</Badge>} {user.isInvited && <Badge>Invited</Badge>}
{user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>} {user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>}
</> </>
} }

View File

@ -28,7 +28,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
) : ( ) : (
"Never signed in" "Never signed in"
)} )}
{!user.lastActiveAt && <Badge>Invited</Badge>} {user.isInvited && <Badge>Invited</Badge>}
{user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>} {user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>}
</> </>
} }

View File

@ -32,7 +32,7 @@ export default class UsersStore extends BaseStore<User> {
@computed @computed
get invited(): User[] { get invited(): User[] {
return filter(this.orderedData, (user) => !user.lastActiveAt); return filter(this.orderedData, (user) => user.isInvited);
} }
@computed @computed