diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 61537ee49..581845474 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,11 +2,54 @@ "version": "2.0.0", "tasks": [ { - "label": "Rebuild website", - "command": "pnpm", - "args": ["run", "turbo:build:website"], + "label": "_root: install", "type": "shell", - "problemMatcher": [] + "command": "devbox run 'pnpm i'" + }, + { + "label": "_root: turbo:prep", + "type": "shell", + "command": "devbox run 'pnpm run turbo:prep'" + }, + { + "label": "_root: turbo:prep:force", + "type": "shell", + "command": "devbox run 'pnpm run turbo:prep:force'" + }, + { + "label": "_root: turbo:test", + "type": "shell", + "command": "devbox run 'pnpm run turbo:test'" + }, + { + "label": "cms: dev", + "type": "shell", + "command": "devbox run 'cd apps/cms && pnpm run dev'" + }, + { + "label": "cms: login", + "type": "shell", + "command": "devbox run 'cd apps/cms && pnpm run login'" + }, + { + "label": "preview: dev", + "type": "shell", + "command": "devbox run 'cd apps/preview && pnpm run start'" + }, + { + "label": "publisher: dev", + "type": "shell", + "command": "devbox run 'cd apps/publisher && pnpm run dev'" + }, + { + "label": "publisher: open", + "type": "shell", + "command": "devbox run 'cd apps/publisher && pnpm run open'" + }, + { + "label": "ui: dev", + "type": "shell", + "command": "devbox run 'cd packages/ui && pnpm run dev'" } ] } diff --git a/apps/preview/package.json b/apps/preview/package.json index f0d45fd57..0b0a657e2 100644 --- a/apps/preview/package.json +++ b/apps/preview/package.json @@ -12,6 +12,7 @@ "start": "node build/index.js" }, "dependencies": { + "@amazeelabs/react-intl": "^1.1.4", "@custom/eslint-config": "workspace:*", "@custom/schema": "workspace:*", "@custom/ui": "workspace:*", @@ -22,8 +23,9 @@ "express-ws": "^5.0.2", "memorystore": "^1.6.7", "node-fetch": "^3.3.2", - "react": "^18", - "react-dom": "^18", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.49.2", "rxjs": "^7.8.1", "simple-oauth2": "^5.1.0" }, diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index 1ebef1475..0266851d4 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -5,6 +5,7 @@ import { useEffect } from 'react'; import { retry } from 'rxjs'; import { webSocket } from 'rxjs/webSocket'; +import StateTransitionForm from './components/StateTransitionForm'; import { drupalExecutor } from './drupal-executor'; declare global { @@ -29,11 +30,13 @@ function App() { const sub = updates$.subscribe((value) => refresh(value)); return sub.unsubscribe; }, [refresh]); + return ( + diff --git a/apps/preview/src/components/StateTransitionForm.tsx b/apps/preview/src/components/StateTransitionForm.tsx new file mode 100644 index 000000000..2fe74dfa0 --- /dev/null +++ b/apps/preview/src/components/StateTransitionForm.tsx @@ -0,0 +1,252 @@ +'use client'; +import { useIntl } from '@amazeelabs/react-intl'; +import { + ModerateContentMutation, + PreviewDrupalPageQuery, +} from '@custom/schema'; +import { useMutation, useOperation } from '@custom/ui/operations'; +import { usePreviewParameters } from '@custom/ui/routes/Preview'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; + +export default function StateTransitionForm() { + const [showForm, setShowForm] = useState(false); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const { data, trigger, isMutating } = useMutation(ModerateContentMutation); + const { ...previewParams } = usePreviewParameters(); + + const { + data: previewData, + isLoading: previewIsLoading, + error: previewError, + } = useOperation(PreviewDrupalPageQuery, previewParams); + + const intl = useIntl(); + + const errorMessages = + !isMutating && + data && + data.moderateContent?.errors && + data.moderateContent.errors.length > 0 + ? data.moderateContent.errors.map((error) => { + return error?.message || ''; + }) + : null; + const successMessage = + !isMutating && data && data.moderateContent?.result === 'success' + ? intl.formatMessage({ + defaultMessage: 'The moderation state has been updated.', + id: 'hZBzvI', + }) + : null; + + if (previewIsLoading) { + return null; + } + if (previewError) { + return ( +
+

+ {intl.formatMessage({ + defaultMessage: + 'The moderation state transition form cannot be displayed because the content failed to load.', + id: 'mFbroE', + })} +

+
+ ); + } + + return ( +
+
+ + {showForm && ( +
+
{ + trigger({ + contentId: previewParams.id, + // We only support the node entity type for now. + entityType: 'node', + revisionId: previewParams.rid || '', + submittedData: JSON.stringify(values), + accessCredentials: { + previewUserId: previewParams.preview_user_id || '', + previewAccessToken: + previewParams.preview_access_token || '', + }, + }); + })} + > + {/* Error / success messages after the form has been submittd. */} + {successMessage ? ( +
    +
  • {successMessage}
  • +
+ ) : null} + {errorMessages ? ( +
    + {errorMessages.map((message, index) => ( +
  • {message}
  • + ))} +
+ ) : null} + {/* Errors from the form validation on client side. */} + {errors && ( +
    + {errors.name && ( +
  • + {intl.formatMessage( + { + defaultMessage: '{field} is required.', + id: 'pc9YT+', + }, + { + field: intl.formatMessage({ + defaultMessage: 'Name', + id: 'HAlOn1', + }), + }, + )} +
  • + )} + {errors.comment && ( +
  • + {intl.formatMessage( + { + defaultMessage: '{field} is required.', + id: 'pc9YT+', + }, + { + field: intl.formatMessage({ + defaultMessage: 'Comment', + id: 'LgbKvU', + }), + }, + )} +
  • + )} +
+ )} +
+ + + {previewData?.preview?.moderationInfo?.currentState?.label} + +
+
+ + +
+
+ + +
+
+ +