[CORL-234] Show user's account history in the user drawer (#2415)

* Create unit tests around the user drawer

CORL-443

* Update comment fixture generation to include reason metadata, action counts

CORL-443

* Add a user status drop down to the user history drawer

CORL-227

* Refactor UserHistoryDrawer tabs and queries for consistency

CORL-234

* Create and empty account history tab in the user drawer

CORL-234

* Create preliminary account history tab queries

Massages the suspension and ban history together into a usable format.
Sorts the ban and suspension history by date, ascending.
Ready to process the history data into components to make up the future
table records.

CORL-234

* Style the user drawer account history records into table format

CORL-234

* Fix typo in admin localization around banned text

CORL-234

* Update suspension record to support templated insertion of timespan

CORL-234

* Create localized parsing of suspension times for account history

CORL-234

* Handle errors when loading the user drawer account history

CORL-234

* Adjust margins of tab content relative to the tab controls

CORL-234

* Extract suspension record time calculations out of component

CORL-234

* Handle when a user has no account history

CORL-234

* Adjust line height to prevent cutting off usernames in community table

CORL-234

* Override hover behaviour for user drawer account history rows

CORL-234

* Adjust default column widths in user drawer - account history

CORL-234

* Use react node type instead of any for component substitution

CORL-234

* Rename TabType to UserTabs

CORL-234

* Use nullable type instead of strict typing using OR's

CORL-234

* Set UserStatusChangeContainer fullWidth default to false

CORL-234

* Combine timed suspension localization into one Localized element

CORL-234

* fix: useMemo on history bar

* Rename UserHistoryDrawerAccountHistory to UserDrawerAccountHistory

Cleans up the agglutination's on these file names.

Probably means we should rename UserHistoryDrawer... to
UserDrawer... everywhere. Will get to that later.

CORL-234

* Reorder imports to match alphabetization preferences

CORL-234

* feat: simplication to action table

* feat: visual tweaks

* chore: added notes for deprecating
This commit is contained in:
Nick Funk
2019-07-29 22:54:53 +00:00
committed by Wyatt Johnson
parent 06a3e4e0a7
commit c069bbe923
23 changed files with 596 additions and 109 deletions
@@ -0,0 +1,19 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
export interface BanActionProps {
action: "created" | "removed";
}
const BanAction: FunctionComponent<BanActionProps> = ({ action }) =>
action === "created" ? (
<Localized id="moderate-user-drawer-account-history-banned">
<span>Banned</span>
</Localized>
) : (
<Localized id="moderate-user-drawer-account-history-ban-removed">
<span>Ban removed</span>
</Localized>
);
export default BanAction;
@@ -0,0 +1,75 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { DURATION_UNIT } from "coral-framework/components";
interface From {
start: Date;
finish: Date;
}
export interface SuspensionActionProps {
action: "created" | "removed" | "ended";
from: From;
}
const UNITS = [
DURATION_UNIT.WEEKS,
DURATION_UNIT.DAYS,
DURATION_UNIT.HOURS,
DURATION_UNIT.MINUTES,
DURATION_UNIT.SECONDS,
];
const UNIT_MAP = {
[DURATION_UNIT.SECONDS]: "second",
[DURATION_UNIT.MINUTES]: "minute",
[DURATION_UNIT.HOURS]: "hour",
[DURATION_UNIT.DAYS]: "day",
[DURATION_UNIT.WEEKS]: "week",
};
function reduceValue(value: number) {
// Find the largest match for the smallest number.
const unit: keyof typeof UNIT_MAP =
UNITS.find(compare => value >= compare) || DURATION_UNIT.SECONDS;
return {
value: Math.round((value / unit) * 100) / 100,
unit: UNIT_MAP[unit],
};
}
const SuspensionAction: FunctionComponent<SuspensionActionProps> = ({
action,
from,
}) => {
if (action === "created") {
const seconds = (from.finish.getTime() - from.start.getTime()) / 1000;
const { value, unit } = reduceValue(seconds);
return (
<Localized
id="moderate-user-drawer-suspension"
$unit={unit}
$value={value}
>
<span>Suspension, {seconds} seconds</span>
</Localized>
);
} else if (action === "removed") {
return (
<Localized id="moderate-user-drawer-account-history-suspension-removed">
<span>Suspension removed</span>
</Localized>
);
}
return (
<Localized id="moderate-user-drawer-account-history-suspension-ended">
<span>Suspension ended</span>
</Localized>
);
};
export default SuspensionAction;
@@ -41,7 +41,7 @@
}
.container {
height: calc(100% - 2 * var(--spacing-2));
height: calc(100% - var(--spacing-2) - var(--spacing-3));
display: flex;
flex-direction: column;
@@ -51,9 +51,10 @@
overflow: hidden;
margin-bottom: var(--spacing-2);
margin-top: var(--spacing-2);
margin-top: var(--spacing-3);
}
.scrollable {
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
}
@@ -4,22 +4,23 @@ import React, { FunctionComponent, useCallback, useState } from "react";
import { Icon, Tab, TabBar, TabContent, TabPane } from "coral-ui/components";
import UserHistoryAllCommentsContainer from "./UserHistoryAllCommentsContainer";
import UserHistoryRejectedCommentsContainer from "./UserHistoryRejectedCommentsContainer";
import UserDrawerAccountHistoryQuery from "./UserDrawerAccountHistoryQuery";
import UserHistoryDrawerAllCommentsQuery from "./UserHistoryDrawerAllCommentsQuery";
import UserHistoryDrawerRejectedCommentsQuery from "./UserHistoryDrawerRejectedCommentsQuery";
import styles from "./UserHistoryTabs.css";
import styles from "./Tabs.css";
type TabType = "ALL" | "REJECTED";
type UserTabs = "ALL_COMMENTS" | "REJECTED_COMMENTS" | "ACCOUNT_HISTORY";
interface Props {
userID: string;
}
const UserHistoryTabs: FunctionComponent<Props> = ({ userID }) => {
const [currentTab, setCurrentTab] = useState<TabType>("ALL");
const [currentTab, setCurrentTab] = useState<UserTabs>("ALL_COMMENTS");
const onTabChanged = useCallback(
(tab: TabType) => {
(tab: UserTabs) => {
setCurrentTab(tab);
},
[setCurrentTab]
@@ -33,10 +34,10 @@ const UserHistoryTabs: FunctionComponent<Props> = ({ userID }) => {
onTabClick={onTabChanged}
className={styles.tabBar}
>
<Tab tabID="ALL" onTabClick={onTabChanged}>
<Tab tabID="ALL_COMMENTS" onTabClick={onTabChanged}>
<div
className={cn(styles.tab, {
[styles.activeTab]: currentTab === "ALL",
[styles.activeTab]: currentTab === "ALL_COMMENTS",
})}
>
<Icon size="sm" className={styles.tabIcon}>
@@ -47,10 +48,10 @@ const UserHistoryTabs: FunctionComponent<Props> = ({ userID }) => {
</Localized>
</div>
</Tab>
<Tab tabID="REJECTED" onTabClick={onTabChanged}>
<Tab tabID="REJECTED_COMMENTS" onTabClick={onTabChanged}>
<div
className={cn(styles.tab, {
[styles.activeTab]: currentTab === "REJECTED",
[styles.activeTab]: currentTab === "REJECTED_COMMENTS",
})}
>
<Icon size="sm" className={styles.tabIcon}>
@@ -61,19 +62,40 @@ const UserHistoryTabs: FunctionComponent<Props> = ({ userID }) => {
</Localized>
</div>
</Tab>
<Tab tabID="ACCOUNT_HISTORY" onTabClick={onTabChanged}>
<div
className={cn(styles.tab, {
[styles.activeTab]: currentTab === "ACCOUNT_HISTORY",
})}
>
<Icon size="sm" className={styles.tabIcon}>
history
</Icon>
<Localized id="moderate-user-drawer-tab-account-history">
<span>Account History</span>
</Localized>
</div>
</Tab>
</TabBar>
<TabContent activeTab={currentTab} className={styles.tabContent}>
<TabPane tabID="ALL">
<TabPane tabID="ALL_COMMENTS">
<div className={styles.container}>
<div className={styles.scrollable}>
<UserHistoryAllCommentsContainer userID={userID} />
<UserHistoryDrawerAllCommentsQuery userID={userID} />
</div>
</div>
</TabPane>
<TabPane tabID="REJECTED">
<TabPane tabID="REJECTED_COMMENTS">
<div className={styles.container}>
<div className={styles.scrollable}>
<UserHistoryRejectedCommentsContainer userID={userID} />
<UserHistoryDrawerRejectedCommentsQuery userID={userID} />
</div>
</div>
</TabPane>
<TabPane tabID="ACCOUNT_HISTORY">
<div className={styles.container}>
<div className={styles.scrollable}>
<UserDrawerAccountHistoryQuery userID={userID} />
</div>
</div>
</TabPane>
@@ -0,0 +1,49 @@
.tableHeader {
border-width: 0px;
border-style: none;
background-color: var(--palette-grey-lightest);
}
.row:first-child:hover {
border-top: none;
}
.row:nth-child(even) {
/* NOTE: differs from pallate colors */
background-color: rgba(242, 242, 242, 0.4);
}
.row:nth-child(even):hover {
/* NOTE: differs from pallate colors */
background-color: rgba(242, 242, 242, 0.4);
box-shadow: none;
}
.row:nth-child(odd):hover {
background-color: transparent;
box-shadow: none;
}
.date {
min-width: 120px;
}
.action {
min-width: 200px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user {
min-width: 200px;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user > * {
vertical-align: sub;
}
@@ -0,0 +1,206 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useMemo } from "react";
import { UserDrawerAccountHistory_user } from "coral-admin/__generated__/UserDrawerAccountHistory_user.graphql";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { graphql, withFragmentContainer } from "coral-framework/lib/relay";
import {
CallOut,
HorizontalGutter,
Icon,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "coral-ui/components";
import BanAction, { BanActionProps } from "./BanAction";
import SuspensionAction, { SuspensionActionProps } from "./SuspensionAction";
import styles from "./UserDrawerAccountHistory.css";
interface Props {
user: UserDrawerAccountHistory_user;
}
interface From {
start: any;
finish: any;
}
interface SuspensionHistoryRecord {
kind: "suspension";
action: SuspensionActionProps;
date: Date;
takenBy: React.ReactNode;
}
interface BanHistoryRecord {
kind: "ban";
action: BanActionProps;
date: Date;
takenBy: React.ReactNode;
}
type HistoryRecord = SuspensionHistoryRecord | BanHistoryRecord;
const UserDrawerAccountHistory: FunctionComponent<Props> = ({ user }) => {
const system = (
<Localized
id="moderate-user-drawer-account-history-system"
icon={<Icon size="md">computer</Icon>}
>
<span>
<Icon size="md">computer</Icon> System
</span>
</Localized>
);
const { locales } = useCoralContext();
const combinedHistory = useMemo(() => {
// Collect all the different types of history items.
const history: HistoryRecord[] = [];
// Merge in all the suspension history items.
user.status.suspension.history.forEach(record => {
const from: From = {
start: new Date(record.from.start),
finish: new Date(record.from.finish),
};
if (record.modifiedAt) {
// Merge in the suspension removals.
history.push({
kind: "suspension",
action: {
action: "removed",
from,
},
date: new Date(record.modifiedAt),
takenBy: record.modifiedBy ? record.modifiedBy.username : system,
});
} else if (!record.active) {
// Merge in the suspension expiries.
history.push({
kind: "suspension",
action: {
action: "ended",
from,
},
date: from.finish,
takenBy: system,
});
}
// Merge in the suspension created.
history.push({
kind: "suspension",
action: {
action: "created",
from,
},
date: new Date(record.createdAt),
takenBy: record.createdBy ? record.createdBy.username : system,
});
});
// Merge in all the ban status history items.
user.status.ban.history.forEach(record => {
history.push({
kind: "ban",
action: {
action: record.active ? "created" : "removed",
},
date: new Date(record.createdAt),
takenBy: record.createdBy ? record.createdBy.username : system,
});
});
// Sort the history so that it's in the right order.
return history.sort((a, b) => b.date.getTime() - a.date.getTime());
}, [user]);
const formatter = new Intl.DateTimeFormat(locales, {
year: "numeric",
month: "long",
day: "numeric",
});
if (combinedHistory.length === 0) {
return (
<CallOut fullWidth>
<Localized id="moderate-user-drawer-account-history-no-history">
No actions have been taken on this account
</Localized>
</CallOut>
);
}
return (
<HorizontalGutter size="double">
<Table fullWidth>
<TableHead className={styles.tableHeader}>
<TableRow>
<TableCell>Date</TableCell>
<TableCell>Action</TableCell>
<TableCell>Taken By</TableCell>
</TableRow>
</TableHead>
<TableBody>
{combinedHistory.map((history, index) => (
<TableRow key={index} className={styles.row}>
<TableCell className={styles.date}>
{formatter.format(history.date)}
</TableCell>
<TableCell className={styles.action}>
{history.kind === "suspension" ? (
<SuspensionAction {...history.action} />
) : (
<BanAction {...history.action} />
)}
</TableCell>
<TableCell className={styles.user}>{history.takenBy}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</HorizontalGutter>
);
};
const enhanced = withFragmentContainer<any>({
user: graphql`
fragment UserDrawerAccountHistory_user on User {
status {
ban {
history {
active
createdBy {
username
}
createdAt
}
}
suspension {
history {
active
from {
start
finish
}
createdBy {
username
}
modifiedAt
modifiedBy {
username
}
createdAt
}
}
}
}
`,
})(UserDrawerAccountHistory);
export default enhanced;
@@ -0,0 +1,10 @@
.spinner {
text-align: center;
}
.callout {
width: 100%;
font-family: var(--font-family-sans-serif);
align-content: center;
text-align: center;
}
@@ -0,0 +1,67 @@
import { graphql, QueryRenderer } from "coral-framework/lib/relay";
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { ReadyState } from "react-relay";
import { UserDrawerAccountHistoryQuery as QueryTypes } from "coral-admin/__generated__/UserDrawerAccountHistoryQuery.graphql";
import { CallOut, Spinner } from "coral-ui/components";
import UserDrawerAccountHistory from "./UserDrawerAccountHistory";
import styles from "./UserDrawerAccountHistoryQuery.css";
interface Props {
userID: string;
}
const UserDrawerAccountHistoryQuery: FunctionComponent<Props> = ({
userID,
}) => {
return (
<QueryRenderer<QueryTypes>
query={graphql`
query UserDrawerAccountHistoryQuery($userID: ID!) {
user(id: $userID) {
...UserDrawerAccountHistory_user
}
}
`}
variables={{ userID }}
cacheConfig={{ force: true }}
render={({ error, props }: ReadyState<QueryTypes["response"]>) => {
if (error) {
return (
<div className={styles.callout}>
<CallOut>{error.message}</CallOut>
</div>
);
}
if (!props) {
return (
<div className={styles.spinner}>
<Spinner />
</div>
);
}
if (!props.user) {
return (
<div className={styles.callout}>
<CallOut>
<Localized id="moderate-user-drawer-user-not-found ">
User not found.
</Localized>
</CallOut>
</div>
);
}
return <UserDrawerAccountHistory user={props.user} />;
}}
/>
);
};
export default UserDrawerAccountHistoryQuery;
@@ -6,24 +6,24 @@ import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { graphql, RelayPaginationProp } from "react-relay";
import { UserHistoryDrawerAllComments_settings } from "coral-admin/__generated__/UserHistoryDrawerAllComments_settings.graphql";
import { UserHistoryDrawerAllComments_user } from "coral-admin/__generated__/UserHistoryDrawerAllComments_user.graphql";
import { UserHistoryDrawerAllComments_viewer } from "coral-admin/__generated__/UserHistoryDrawerAllComments_viewer.graphql";
import { UserHistoryDrawerAllCommentsPaginationQueryVariables } from "coral-admin/__generated__/UserHistoryDrawerAllCommentsPaginationQuery.graphql";
import { ModerateCardContainer } from "coral-admin/components/ModerateCard";
import { Button, CallOut, Typography } from "coral-ui/components";
import { UserHistoryAllComments_settings } from "coral-admin/__generated__/UserHistoryAllComments_settings.graphql";
import { UserHistoryAllComments_user } from "coral-admin/__generated__/UserHistoryAllComments_user.graphql";
import { UserHistoryAllComments_viewer } from "coral-admin/__generated__/UserHistoryAllComments_viewer.graphql";
import { UserHistoryAllCommentsPaginationQueryVariables } from "coral-admin/__generated__/UserHistoryAllCommentsPaginationQuery.graphql";
import styles from "./UserHistoryAllComments.css";
import styles from "./UserHistoryDrawerAllComments.css";
interface Props {
user: UserHistoryAllComments_user;
viewer: UserHistoryAllComments_viewer;
settings: UserHistoryAllComments_settings;
user: UserHistoryDrawerAllComments_user;
viewer: UserHistoryDrawerAllComments_viewer;
settings: UserHistoryDrawerAllComments_settings;
relay: RelayPaginationProp;
}
const UserHistoryAllComments: FunctionComponent<Props> = ({
const UserHistoryDrawerAllComments: FunctionComponent<Props> = ({
user,
viewer,
settings,
@@ -85,33 +85,33 @@ const UserHistoryAllComments: FunctionComponent<Props> = ({
);
};
type FragmentVariables = UserHistoryAllCommentsPaginationQueryVariables;
type FragmentVariables = UserHistoryDrawerAllCommentsPaginationQueryVariables;
const enhanced = withPaginationContainer<
Props,
UserHistoryAllCommentsPaginationQueryVariables,
UserHistoryDrawerAllCommentsPaginationQueryVariables,
FragmentVariables
>(
{
viewer: graphql`
fragment UserHistoryAllComments_viewer on User {
fragment UserHistoryDrawerAllComments_viewer on User {
...ModerateCardContainer_viewer
}
`,
settings: graphql`
fragment UserHistoryAllComments_settings on Settings {
fragment UserHistoryDrawerAllComments_settings on Settings {
...ModerateCardContainer_settings
}
`,
user: graphql`
fragment UserHistoryAllComments_user on User
fragment UserHistoryDrawerAllComments_user on User
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
) {
username
allComments(first: $count, after: $cursor)
@connection(key: "UserHistoryAll_allComments") {
@connection(key: "UserHistoryDrawer_allComments") {
edges {
node {
id
@@ -141,18 +141,18 @@ const enhanced = withPaginationContainer<
};
},
query: graphql`
query UserHistoryAllCommentsPaginationQuery(
query UserHistoryDrawerAllCommentsPaginationQuery(
$userID: ID!
$count: Int!
$cursor: Cursor
) {
user(id: $userID) {
...UserHistoryAllComments_user
...UserHistoryDrawerAllComments_user
@arguments(count: $count, cursor: $cursor)
}
}
`,
}
)(UserHistoryAllComments);
)(UserHistoryDrawerAllComments);
export default enhanced;
@@ -4,32 +4,32 @@ import { ReadyState } from "react-relay";
import { CallOut, Spinner } from "coral-ui/components";
import { UserHistoryAllCommentsContainerQuery as QueryTypes } from "coral-admin/__generated__/UserHistoryAllCommentsContainerQuery.graphql";
import { UserHistoryDrawerAllCommentsQuery as QueryTypes } from "coral-admin/__generated__/UserHistoryDrawerAllCommentsQuery.graphql";
import { graphql, QueryRenderer } from "coral-framework/lib/relay";
import UserHistoryAllComments from "./UserHistoryAllComments";
import UserHistoryDrawerAllComments from "./UserHistoryDrawerAllComments";
import styles from "./UserHistoryAllCommentsContainer.css";
import styles from "./UserHistoryDrawerAllCommentsQuery.css";
interface Props {
userID: string;
}
const UserHistoryAllCommentsContainer: FunctionComponent<Props> = ({
const UserHistoryDrawerAllCommentsQuery: FunctionComponent<Props> = ({
userID,
}) => {
return (
<QueryRenderer<QueryTypes>
query={graphql`
query UserHistoryAllCommentsContainerQuery($userID: ID!) {
query UserHistoryDrawerAllCommentsQuery($userID: ID!) {
user(id: $userID) {
...UserHistoryAllComments_user
...UserHistoryDrawerAllComments_user
}
viewer {
...UserHistoryAllComments_viewer
...UserHistoryDrawerAllComments_viewer
}
settings {
...UserHistoryAllComments_settings
...UserHistoryDrawerAllComments_settings
}
}
`}
@@ -57,7 +57,7 @@ const UserHistoryAllCommentsContainer: FunctionComponent<Props> = ({
}
return (
<UserHistoryAllComments
<UserHistoryDrawerAllComments
// We can never get to this part of the UI without being logged in.
viewer={props.viewer!}
settings={props.settings!}
@@ -69,4 +69,4 @@ const UserHistoryAllCommentsContainer: FunctionComponent<Props> = ({
);
};
export default UserHistoryAllCommentsContainer;
export default UserHistoryDrawerAllCommentsQuery;
@@ -99,18 +99,23 @@ hr {
}
.userStatusLabel {
margin-right: var(--spacing-1);
margin-bottom: 2px;
display: inline-block;
line-height: calc(36em / 24);
margin-right: var(--spacing-1);
font-family: var(--font-family-sans-serif);
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 14px;
}
.userStatusChange {
display: inline-block;
border-style: solid;
border-color: var(--palette-grey-lighter);
border-width: 1px;
border-radius: 2px;
padding-left: var(--spacing-2);
padding-right: var(--spacing-1);
}
@@ -1,10 +1,12 @@
import { graphql, QueryRenderer } from "coral-framework/lib/relay";
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { ReadyState } from "react-relay";
import { UserHistoryDrawerQuery as QueryTypes } from "coral-admin/__generated__/UserHistoryDrawerQuery.graphql";
import { UserStatusChangeContainer } from "coral-admin/components/UserStatus";
import { CopyButton } from "coral-framework/components";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { graphql, QueryRenderer } from "coral-framework/lib/relay";
import {
Button,
CallOut,
@@ -14,9 +16,7 @@ import {
Typography,
} from "coral-ui/components";
import { UserHistoryDrawerQuery as QueryTypes } from "coral-admin/__generated__/UserHistoryDrawerQuery.graphql";
import UserHistoryTabs from "./UserHistoryTabs";
import Tabs from "./Tabs";
import styles from "./UserHistoryDrawerQuery.css";
@@ -31,6 +31,13 @@ const UserHistoryDrawerQuery: FunctionComponent<Props> = ({
userID,
onClose,
}) => {
const { locales } = useCoralContext();
const formatter = new Intl.DateTimeFormat(locales, {
month: "long",
day: "numeric",
year: "numeric",
});
return (
<QueryRenderer<QueryTypes>
query={graphql`
@@ -78,20 +85,10 @@ const UserHistoryDrawerQuery: FunctionComponent<Props> = ({
<span>{user.username}</span>
</Flex>
<div className={styles.userStatus}>
<Flex alignItems="center">
<div className={styles.userStatusLabel}>
<Typography variant="bodyCopyBold" container="div">
<Flex alignItems="center" itemGutter="half">
<Localized id="moderate-user-drawer-status-label">
Status:
</Localized>
</Flex>
</Typography>
</div>
<div className={styles.userStatusChange}>
<UserStatusChangeContainer user={user} fullWidth={false} />
</div>
</Flex>
<div className={styles.userStatusLabel}>Status:</div>
<div className={styles.userStatusChange}>
<UserStatusChangeContainer user={user} />
</div>
</div>
<div className={styles.userDetails}>
<Flex alignItems="center" className={styles.userDetail}>
@@ -130,11 +127,7 @@ const UserHistoryDrawerQuery: FunctionComponent<Props> = ({
</Icon>
</Localized>
<Typography variant="bodyCopy" container="span">
{new Date(user.createdAt).toLocaleDateString("en-us", {
month: "long",
day: "numeric",
year: "numeric",
})}
{formatter.format(new Date(user.createdAt))}
</Typography>
</Flex>
<Flex alignItems="center" className={styles.userDetail}>
@@ -162,7 +155,7 @@ const UserHistoryDrawerQuery: FunctionComponent<Props> = ({
</div>
<hr className={styles.divider} />
<div className={styles.comments}>
<UserHistoryTabs userID={user.id} />
<Tabs userID={user.id} />
</div>
</>
);
@@ -6,24 +6,24 @@ import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { graphql, RelayPaginationProp } from "react-relay";
import { UserHistoryDrawerRejectedComments_settings } from "coral-admin/__generated__/UserHistoryDrawerRejectedComments_settings.graphql";
import { UserHistoryDrawerRejectedComments_user } from "coral-admin/__generated__/UserHistoryDrawerRejectedComments_user.graphql";
import { UserHistoryDrawerRejectedComments_viewer } from "coral-admin/__generated__/UserHistoryDrawerRejectedComments_viewer.graphql";
import { UserHistoryDrawerRejectedCommentsPaginationQueryVariables } from "coral-admin/__generated__/UserHistoryDrawerRejectedCommentsPaginationQuery.graphql";
import { ModerateCardContainer } from "coral-admin/components/ModerateCard";
import { Button, CallOut, Typography } from "coral-ui/components";
import { UserHistoryRejectedComments_settings } from "coral-admin/__generated__/UserHistoryRejectedComments_settings.graphql";
import { UserHistoryRejectedComments_user } from "coral-admin/__generated__/UserHistoryRejectedComments_user.graphql";
import { UserHistoryRejectedComments_viewer } from "coral-admin/__generated__/UserHistoryRejectedComments_viewer.graphql";
import { UserHistoryRejectedCommentsPaginationQueryVariables } from "coral-admin/__generated__/UserHistoryRejectedCommentsPaginationQuery.graphql";
import styles from "./UserHistoryRejectedComments.css";
import styles from "./UserHistoryDrawerRejectedComments.css";
interface Props {
user: UserHistoryRejectedComments_user;
viewer: UserHistoryRejectedComments_viewer;
settings: UserHistoryRejectedComments_settings;
user: UserHistoryDrawerRejectedComments_user;
viewer: UserHistoryDrawerRejectedComments_viewer;
settings: UserHistoryDrawerRejectedComments_settings;
relay: RelayPaginationProp;
}
const UserHistoryRejectedComments: FunctionComponent<Props> = ({
const UserHistoryDrawerRejectedComments: FunctionComponent<Props> = ({
user,
viewer,
settings,
@@ -87,33 +87,33 @@ const UserHistoryRejectedComments: FunctionComponent<Props> = ({
);
};
type FragmentVariables = UserHistoryRejectedCommentsPaginationQueryVariables;
type FragmentVariables = UserHistoryDrawerRejectedCommentsPaginationQueryVariables;
const enhanced = withPaginationContainer<
Props,
UserHistoryRejectedCommentsPaginationQueryVariables,
UserHistoryDrawerRejectedCommentsPaginationQueryVariables,
FragmentVariables
>(
{
viewer: graphql`
fragment UserHistoryRejectedComments_viewer on User {
fragment UserHistoryDrawerRejectedComments_viewer on User {
...ModerateCardContainer_viewer
}
`,
settings: graphql`
fragment UserHistoryRejectedComments_settings on Settings {
fragment UserHistoryDrawerRejectedComments_settings on Settings {
...ModerateCardContainer_settings
}
`,
user: graphql`
fragment UserHistoryRejectedComments_user on User
fragment UserHistoryDrawerRejectedComments_user on User
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
) {
username
rejectedComments(first: $count, after: $cursor)
@connection(key: "UserHistoryRejected_rejectedComments") {
@connection(key: "UserHistoryDrawer_rejectedComments") {
edges {
node {
id
@@ -143,18 +143,18 @@ const enhanced = withPaginationContainer<
};
},
query: graphql`
query UserHistoryRejectedCommentsPaginationQuery(
query UserHistoryDrawerRejectedCommentsPaginationQuery(
$userID: ID!
$count: Int!
$cursor: Cursor
) {
user(id: $userID) {
...UserHistoryRejectedComments_user
...UserHistoryDrawerRejectedComments_user
@arguments(count: $count, cursor: $cursor)
}
}
`,
}
)(UserHistoryRejectedComments);
)(UserHistoryDrawerRejectedComments);
export default enhanced;
@@ -2,34 +2,34 @@ import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { ReadyState } from "react-relay";
import { UserHistoryDrawerRejectedCommentsQuery as QueryTypes } from "coral-admin/__generated__/UserHistoryDrawerRejectedCommentsQuery.graphql";
import { graphql, QueryRenderer } from "coral-framework/lib/relay";
import { CallOut, Spinner } from "coral-ui/components";
import { UserHistoryRejectedCommentsContainerQuery as QueryTypes } from "coral-admin/__generated__/UserHistoryRejectedCommentsContainerQuery.graphql";
import { graphql, QueryRenderer } from "coral-framework/lib/relay";
import UserHistoryDrawerRejectedComments from "./UserHistoryDrawerRejectedComments";
import UserHistoryRejectedComments from "./UserHistoryRejectedComments";
import styles from "./UserHistoryRejectedCommentsContainer.css";
import styles from "./UserHistoryDrawerRejectedCommentsQuery.css";
interface Props {
userID: string;
}
const UserHistoryRejectedCommentsContainer: FunctionComponent<Props> = ({
const UserHistoryDrawerRejectedCommentsQuery: FunctionComponent<Props> = ({
userID,
}) => {
return (
<QueryRenderer<any>
query={graphql`
query UserHistoryRejectedCommentsContainerQuery($userID: ID!) {
query UserHistoryDrawerRejectedCommentsQuery($userID: ID!) {
user(id: $userID) {
...UserHistoryRejectedComments_user
...UserHistoryDrawerRejectedComments_user
}
viewer {
...UserHistoryRejectedComments_viewer
...UserHistoryDrawerRejectedComments_viewer
}
settings {
...UserHistoryRejectedComments_settings
...UserHistoryDrawerRejectedComments_settings
}
}
`}
@@ -57,7 +57,7 @@ const UserHistoryRejectedCommentsContainer: FunctionComponent<Props> = ({
}
return (
<UserHistoryRejectedComments
<UserHistoryDrawerRejectedComments
// We can never get to this part of the UI without being logged in.
viewer={props.viewer!}
settings={props.settings!}
@@ -69,4 +69,4 @@ const UserHistoryRejectedCommentsContainer: FunctionComponent<Props> = ({
);
};
export default UserHistoryRejectedCommentsContainer;
export default UserHistoryDrawerRejectedCommentsQuery;
@@ -22,7 +22,7 @@ interface Props {
const UserStatusChangeContainer: FunctionComponent<Props> = ({
user,
fullWidth = true,
fullWidth = false,
}) => {
const banUser = useMutation(BanUserMutation);
const removeUserBan = useMutation(RemoveUserBanMutation);
@@ -27,5 +27,5 @@
font-family: var(--font-family-sans-serif);
font-weight: var(--font-weight-regular);
font-size: calc(14rem / var(--rem-base));
line-height: calc(14em / 14);
line-height: calc(16em / 14);
}
@@ -53,7 +53,7 @@ const UserRow: FunctionComponent<Props> = ({
<UserRole user={user} viewer={viewer} />
</TableCell>
<TableCell className={styles.statusColumn}>
<UserStatus user={user} />
<UserStatus user={user} fullWidth />
</TableCell>
</TableRow>
);
@@ -32,7 +32,6 @@ Notes:
*/
/*
import {
createSettings,
createStory,
+41
View File
@@ -436,12 +436,53 @@ moderate-user-drawer-member-id =
.title = Member ID
moderate-user-drawer-tab-all-comments = All Comments
moderate-user-drawer-tab-rejected-comments = Rejected
moderate-user-drawer-tab-account-history = Account History
moderate-user-drawer-load-more = Load More
moderate-user-drawer-all-no-comments = {$username} has not submitted any comments.
moderate-user-drawer-rejected-no-comments = {$username} does not have any rejected comments.
moderate-user-drawer-user-not-found = User not found.
moderate-user-drawer-status-label = Status:
moderate-user-drawer-account-history-system = <icon>computer</icon> System
moderate-user-drawer-account-history-suspension-ended = Suspension ended
moderate-user-drawer-account-history-suspension-removed = Suspension removed
moderate-user-drawer-account-history-banned = Banned
moderate-user-drawer-account-history-ban-removed = Ban removed
moderate-user-drawer-account-history-no-history = No actions have been taken on this account
moderate-user-drawer-suspension =
Suspension, { $value } { $unit ->
[second] { $value ->
[1] second
*[other] seconds
}
[minute] { $value ->
[1] minute
*[other] minutes
}
[hour] { $value ->
[1] hour
*[other] hours
}
[day] { $value ->
[1] day
*[other] days
}
[week] { $value ->
[1] week
*[other] weeks
}
[month] { $value ->
[1] month
*[other] months
}
[year] { $value ->
[1] year
*[other] years
}
*[other] unknown unit
}
## Create Username
createUsername-createUsernameHeader = Create Username