mirror of
https://github.com/wassname/talk.git
synced 2026-08-17 11:27:52 +08:00
[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:
@@ -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;
|
||||
+4
-3
@@ -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;
|
||||
}
|
||||
+36
-14
@@ -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;
|
||||
+19
-19
@@ -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;
|
||||
+10
-10
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
+19
-19
@@ -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;
|
||||
+12
-12
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user