diff --git a/vocodes/src/common/ApiConfig.ts b/vocodes/src/common/ApiConfig.ts index 1cacd7924c..dd1d321ecd 100644 --- a/vocodes/src/common/ApiConfig.ts +++ b/vocodes/src/common/ApiConfig.ts @@ -266,6 +266,10 @@ class ApiConfig { return `${this.getScheme()}://${this.getNewApiHost()}/moderation/jobs/w2l_inference_queue_stats`; } + getModerationPendingW2lTemplates(): string { + return `${this.getScheme()}://${this.getNewApiHost()}/moderation/pending/w2l_templates`; + } + private getScheme() : string { return this.isLocalDev ? "http" : "https"; } diff --git a/vocodes/src/v2/api/moderation/GetPendingW2lTemplates.ts b/vocodes/src/v2/api/moderation/GetPendingW2lTemplates.ts new file mode 100644 index 0000000000..7ca1dde2ab --- /dev/null +++ b/vocodes/src/v2/api/moderation/GetPendingW2lTemplates.ts @@ -0,0 +1,77 @@ +import { ApiConfig } from "../../../common/ApiConfig"; + +export interface PendingW2lTemplates { + success: boolean, + templates: Array +} + +export interface PendingW2lTemplatesEntryForList { + template_token: string, + title: string, + template_type: string, + duration_millis: number, + frame_width: number, + frame_height: number, + creator_user_token: string, + creator_username: string, + creator_display_name: string, + creator_gravatar_hash: string, + created_at: Date, +} + +export enum PendingW2lTemplatesLookupError { + NotAuthorized, + ServerError, + FrontendError, +} + +export type GetPendingW2lTemplatesResponse = PendingW2lTemplates | PendingW2lTemplatesLookupError; + +export function GetPendingW2lTemplatesIsOk(response: GetPendingW2lTemplatesResponse): response is PendingW2lTemplates { + return response.hasOwnProperty('templates'); +} + +export function GetPendingW2lTemplatesIsErr(response: GetPendingW2lTemplatesResponse): response is PendingW2lTemplatesLookupError { + return !response.hasOwnProperty('templates'); +} + +interface PendingW2lTemplatesResponsePayload { + success: boolean, + error_reason?: string, + templates?: PendingW2lTemplates, +} + + +export async function GetPendingW2lTemplates() : Promise +{ + const endpoint = new ApiConfig().getModerationPendingW2lTemplates(); + + return await fetch(endpoint, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include', + }) + .then(res => res.json()) + .then(res => { + const response : PendingW2lTemplatesResponsePayload = res; + + if (response?.success) { + return res as PendingW2lTemplates; // TODO: This is not the way. + } + + if (response?.success === false) { + if (response.error_reason?.includes("authorized")) { + return PendingW2lTemplatesLookupError.NotAuthorized; + } else { + return PendingW2lTemplatesLookupError.ServerError; + } + } + + return PendingW2lTemplatesLookupError.FrontendError; + }) + .catch(e => { + return PendingW2lTemplatesLookupError.FrontendError; + }); +} \ No newline at end of file diff --git a/vocodes/src/v2/view/NewVocodesContainer.tsx b/vocodes/src/v2/view/NewVocodesContainer.tsx index 6cb6478dfa..9ae4a051c4 100644 --- a/vocodes/src/v2/view/NewVocodesContainer.tsx +++ b/vocodes/src/v2/view/NewVocodesContainer.tsx @@ -40,6 +40,7 @@ import { ProfileBanFc } from './profile/profile_ban/ProfileBanFc'; import { ModerationUserListFc } from './moderation/moderation_user_list/ModerationUserList'; import { LeaderboardFc } from './leaderboard/LeaderboardFc'; import { ModerationJobStatsFc } from './moderation/moderation_job_stats/ModerationJobStatsFc'; +import { ModerationPendingW2lTemplatesFc } from './moderation/moderation_pending_w2l_templates/ModerationPendingW2lTemplatesFc'; interface Props { sessionWrapper: SessionWrapper, @@ -285,6 +286,12 @@ class NewVocodesContainer extends React.Component { /> + + + + + ); +} + diff --git a/vocodes/src/v2/view/moderation/moderation_main/ModerationFc.tsx b/vocodes/src/v2/view/moderation/moderation_main/ModerationFc.tsx index a67f43a1c6..3448cc115b 100644 --- a/vocodes/src/v2/view/moderation/moderation_main/ModerationFc.tsx +++ b/vocodes/src/v2/view/moderation/moderation_main/ModerationFc.tsx @@ -37,6 +37,13 @@ function ModerationFc(props: Props) {
+ Unapproved W2L Templates + +
+

More mod controls will be added in the future: user roles, activity tracking, timed bans, account bans, etc. diff --git a/vocodes/src/v2/view/moderation/moderation_pending_w2l_templates/ModerationPendingW2lTemplatesFc.tsx b/vocodes/src/v2/view/moderation/moderation_pending_w2l_templates/ModerationPendingW2lTemplatesFc.tsx new file mode 100644 index 0000000000..03b6b71035 --- /dev/null +++ b/vocodes/src/v2/view/moderation/moderation_pending_w2l_templates/ModerationPendingW2lTemplatesFc.tsx @@ -0,0 +1,118 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { SessionWrapper } from '../../../../session/SessionWrapper'; +import { formatDistance } from 'date-fns'; +import { FrontendUrlConfig } from '../../../../common/FrontendUrlConfig'; +import { GravatarFc } from '../../_common/GravatarFc'; +import { BackLink } from '../../_common/BackLink'; +import { GetPendingW2lTemplates, GetPendingW2lTemplatesIsOk, PendingW2lTemplatesEntryForList } from '../../../api/moderation/GetPendingW2lTemplates'; +import { PhotoVideoIcon } from '../../_icons/PhotoVideoIcon'; + +interface Props { + sessionWrapper: SessionWrapper, +} + +function ModerationPendingW2lTemplatesFc(props: Props) { + const [templates, setTemplates] = useState>([]); + + const getUsers = useCallback(async () => { + const response = await GetPendingW2lTemplates(); + + if (GetPendingW2lTemplatesIsOk(response)) { + setTemplates(response.templates); + } + + }, []); + + useEffect(() => { + getUsers(); + }, [getUsers]); + + + if (!templates) { + return

+ } + + if (!props.sessionWrapper.canApproveW2lTemplates()) { + return

Unauthorized

; + } + + const now = new Date(); + let rows : Array = []; + + templates.forEach(template=> { + const createTime = new Date(template.created_at); + const relativeCreateTime = formatDistance(createTime, now, { addSuffix: true }); + + rows.push( + + + + +   + {template.title} + + + + + +   + {template.creator_display_name} + + + {template.template_type} + {relativeCreateTime} + + ) + }); + + return ( +
+

Unapproved W2L Templates

+ + + +
+
+ +

+ This lists the templates that haven't yet been approved for display. +

+ +
+ +

+ Don't delete templates unless they overwhelmingly break the rules. + Users can use templates in private without them necessarily being approved for public use. +

+ +
+ +

+ Setting approved = TRUE will show the template on the main site for everyone to use. + Setting approved = FALSE will still let the author and people directly accessing via + URL to use the template, but will not make it public. + It will also remove it from the moderation queue. +

+ + +
+ + + + + + + + + + + + {rows} + +
TemplateCreatorTypeCreated At
+
+ ) +} + +export { ModerationPendingW2lTemplatesFc };