Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions src/core/client/admin/routeConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ import ConfigureOrganizationRouteContainer from "./routes/configure/sections/org
import ConfigureWordListRouteContainer from "./routes/configure/sections/wordList/containers/WordListRouteContainer";
import LoginContainer from "./routes/login/containers/LoginContainer";
import ModerateContainer from "./routes/moderate/containers/ModerateContainer";
import {
PendingQueueContainer,
ReportedQueueContainer,
UnmoderatedQueueContainer,
} from "./routes/moderate/containers/QueueContainer";
import PendingQueueContainer from "./routes/moderate/containers/PendingQueueContainer";
import ReportedQueueContainer from "./routes/moderate/containers/ReportedQueueContainer";
import UnmoderatedQueueContainer from "./routes/moderate/containers/UnmoderatedQueueContainer";
import RejectedQueueContainer from "./routes/moderate/containers/RejectedQueueContainer";
import SingleModerateContainer from "./routes/moderate/containers/SingleModerateContainer";
import Stories from "./routes/stories/components/Stories";
Expand All @@ -37,10 +35,7 @@ export default makeRouteConfig(
<Redirect from="/" to="/admin/moderate/reported" />
<Route path="reported" {...ReportedQueueContainer.routeConfig} />
<Route path="pending" {...PendingQueueContainer.routeConfig} />
<Route
path="unmoderated"
{...UnmoderatedQueueContainer.routeConfig}
/>
<Route path="unmoderated" {...UnmoderatedQueueContainer.routeConfig} />
<Route path="rejected" {...RejectedQueueContainer.routeConfig} />
</Route>
<Route path="stories" {...StoriesContainer.routeConfig} />
Expand Down
76 changes: 42 additions & 34 deletions src/core/client/admin/routes/moderate/components/Queue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ import React, { StatelessComponent } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";

import AutoLoadMoreContainer from "talk-admin/containers/AutoLoadMoreContainer";
import { Flex, HorizontalGutter } from "talk-ui/components";
import { Flex, HorizontalGutter, Card } from "talk-ui/components";
import { PropTypesOf } from "talk-ui/types";

import ModerateCardContainer from "../containers/ModerateCardContainer";

import styles from "./Queue.css";

export type QueueComments = Array<{ id: string } & PropTypesOf<typeof ModerateCardContainer>["comment"]>;
export type QueueSettings = PropTypesOf<typeof ModerateCardContainer>["settings"];

interface Props {
comments: Array<
{ id: string } & PropTypesOf<typeof ModerateCardContainer>["comment"]
>;
settings: PropTypesOf<typeof ModerateCardContainer>["settings"];
comments: QueueComments;
settings: QueueSettings;
onLoadMore: () => void;
hasMore: boolean;
disableLoadMore: boolean;
danglingLogic: PropTypesOf<typeof ModerateCardContainer>["danglingLogic"];
emptyMessage?: string;
}

const Queue: StatelessComponent<Props> = ({
Expand All @@ -27,36 +29,42 @@ const Queue: StatelessComponent<Props> = ({
disableLoadMore,
onLoadMore,
danglingLogic,
emptyMessage,
}) => (
<HorizontalGutter className={styles.root} size="double">
<TransitionGroup component={null} appear={false} enter={false} exit>
{comments.map(c => (
<CSSTransition
key={c.id}
timeout={400}
classNames={{
exit: styles.exitTransition,
exitActive: styles.exitTransitionActive,
exitDone: styles.exitTransitionDone,
}}
>
<ModerateCardContainer
settings={settings}
comment={c}
danglingLogic={danglingLogic}
<HorizontalGutter className={styles.root} size="double">
<TransitionGroup component={null} appear={false} enter={false} exit>
{comments.map(c => (
<CSSTransition
key={c.id}
timeout={400}
classNames={{
exit: styles.exitTransition,
exitActive: styles.exitTransitionActive,
exitDone: styles.exitTransitionDone,
}}
>
<ModerateCardContainer
settings={settings}
comment={c}
danglingLogic={danglingLogic}
/>
</CSSTransition>
))}
{comments.length === 0 && emptyMessage !== undefined && (
<Card>
<span>{emptyMessage}</span>
</Card>
)}
</TransitionGroup>
{hasMore && (
<Flex justifyContent="center">
<AutoLoadMoreContainer
disableLoadMore={disableLoadMore}
onLoadMore={onLoadMore}
/>
</CSSTransition>
))}
</TransitionGroup>
{hasMore && (
<Flex justifyContent="center">
<AutoLoadMoreContainer
disableLoadMore={disableLoadMore}
onLoadMore={onLoadMore}
/>
</Flex>
)}
</HorizontalGutter>
);
</Flex>
)}
</HorizontalGutter>
);

export default Queue;
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { RouteProps } from "found";
import React from "react";
import { graphql, GraphQLTaggedNode } from "react-relay";

import { QueueContainer_queue as QueueData } from "talk-admin/__generated__/QueueContainer_queue.graphql";
import { QueueContainer_settings as SettingsData } from "talk-admin/__generated__/QueueContainer_settings.graphql";
import { QueueContainerPaginationPendingQueryVariables } from "talk-admin/__generated__/QueueContainerPaginationPendingQuery.graphql";
import { withPaginationContainer } from "talk-framework/lib/relay";

import LoadingQueue from "../components/LoadingQueue";
import QueueContainer, { QueueContainerProps } from "./QueueContainer";
import { QueueComments, QueueSettings } from "../components/Queue";

// TODO: (cvle) If this could be autogenerated..
type FragmentVariables = QueueContainerPaginationPendingQueryVariables;

export interface ModerationQueueContainerProps extends QueueContainerProps {
queue: QueueData;
settings: SettingsData;
}

export default abstract class ModerationQueueContainer extends QueueContainer<ModerationQueueContainerProps> {

protected getComments(): QueueComments {
return this.props.queue.comments.edges.map(edge => edge.node);
}

protected getSettings(): QueueSettings {
return this.props.settings;
}

protected static enhanceWithPagination(
component: React.ComponentType<ModerationQueueContainerProps>,
paginationQuery: GraphQLTaggedNode
): React.ComponentType<any> {
return withPaginationContainer<
ModerationQueueContainerProps,
QueueContainerPaginationPendingQueryVariables,
FragmentVariables
>(
{
queue: graphql`
fragment QueueContainer_queue on ModerationQueue
@argumentDefinitions(
count: { type: "Int!", defaultValue: 5 }
cursor: { type: "Cursor" }
) {
count
comments(first: $count, after: $cursor)
@connection(key: "Queue_comments") {
edges {
node {
id
...ModerateCardContainer_comment
}
}
}
}
`,
settings: graphql`
fragment QueueContainer_settings on Settings {
...ModerateCardContainer_settings
}
`,
},
{
direction: "forward",
getConnectionFromProps(props) {
return props.queue && props.queue.comments;
},
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, { count, cursor }, fragmentVariables) {
return {
count,
cursor,
};
},
query: paginationQuery,
}
)(component);
}

protected static createRouteConfig(
component: React.ComponentType<ModerationQueueContainerProps>,
queueQuery: GraphQLTaggedNode,
): RouteProps {
return {
Component: component,
query: queueQuery,
cacheConfig: { force: true },
render: ({ Component, props }) => {
const anyProps = props as any;
if (Component && props) {
const queue =
anyProps.moderationQueues[Object.keys(anyProps.moderationQueues)[0]];
return <Component queue={queue} settings={anyProps.settings} />;
}
return <LoadingQueue />;
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { RouteProps } from "found";
import { graphql } from "react-relay";
import ModerationQueueContainer from "./ModerationQueueContainer";

export default class PendingQueueContainer extends ModerationQueueContainer {
public static routeConfig = PendingQueueContainer.createRouteConfig();

protected getEmptyQueueMessage(): string {
return "Nicely done! There are no more pending comments to moderate.";
}

protected static createRouteConfig(): RouteProps {
const queueQuery = graphql`
query QueueContainerPendingQuery {
moderationQueues {
pending {
...QueueContainer_queue
}
}
settings {
...QueueContainer_settings
}
}
`;

const paginationQuery = graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query QueueContainerPaginationPendingQuery($count: Int!, $cursor: Cursor) {
moderationQueues {
pending {
...QueueContainer_queue @arguments(count: $count, cursor: $cursor)
}
}
}
`;

const paginatedQueueContainer = ModerationQueueContainer.enhanceWithPagination(
PendingQueueContainer,
paginationQuery
);

return ModerationQueueContainer.createRouteConfig(paginatedQueueContainer, queueQuery);
}
}
Loading