[CORL-729] Upgrade Final Form & improve tests (#2735)

* CORL-729 Upgrade final form, fix and improve tests

This is a squashed rebase from these commits:
a300b31c23ab11e5e6f0668bc03ece7697360aaa feat: error on optimisic response warnings during test
dd8a9776865ec41d346e23ae0743d0d4fb0caa21 fix: turn off @typescript-eslint/prefer-regexp-exec rule
b995daacf1722cace60d755e672cb6a3a20d6bc2 feat: mute false warnings in test
e44f9e28307cd63a82c1fb7ac013667dd7b7bc46 fix: wrap remaining tests with act
afbd4329b97f3dbc9f873ea4ff234d98bb651ccf feat: fail when act(async () => ...) without await warnings
51dfb60b7d75411ba2e1a28db33f4aba5cf84de1 feat: fail tests with act warnings
97f93546ed8113e207882411eb4cdb7675b0796c fix: mock window.resizeTo globally in tests
850958b8c4d2fc0aca67ae580296433af223f8ab fix: more tests with act
24c05ab88e9a416e4962acc3f20f2c764ba07657 fix: wrap charCountEditComment test in acts
ed590b82d147470bba74055dc682e6b70d2e76fb fix signInWithEmail
4a2b9402cb6ce9565d99ae1a950eaa422ff603c3 fix: PostCommentFormContainer
815ebe6ef364d954d4bd0a35495934c9d014170b fix: use final form initialize instead of reset and remove obsolete
d3101f2112ed3ffe8d06609620e31e6655d2cf6d Merge branch 'feature/CORL-729-final-form' of https://github.com/coralproject/talk into feature/CORL-729-final-form
a0658da610a5f39b6fae78ffb8dd291b22d54e50 fix: addEmailAddress
60f7fc99a44dfa49dfd401a0ef49c60973b3e8e3 fix: use proper act pattern in renderConfigure.spec.tsx
d66bdfc2245c2b1ee03a1b3a3a56f1d5ba14ddc4 Stop propagation of Modal content click events (#2706)
ec6689594136e22a5b9f05ea284162702dc4955d fix: use proper act pattern in createUsername.spec.tsx
ef239496964a5f9a91ee1c4424ad81537c4f47fe fix: stream configuration
c7e06a0c6aef6b299c41392af81f8a20855028bf fix: user proper act pattern in streamConfiguration
9712e659e394a898500ed649464ff14d4870e589 Merge branch 'feature/CORL-729-final-form' of https://github.com/coralproject/talk into feature/CORL-729-final-form
9e5cfbaf3593615b457055de23f187fa07edd4c4 fix: signIn test
99b44a4a1bbb7ff2cd44c6821ad33b80f90c4a99 fix: user proper act pattern in stories.spec.tsx
ed7c1a92f93ab9aaa85ff92837b0ed21560cb358 fix: user proper act pattern in addEmailAddress
a04b392cb2148b9a24791b062027796c409d053e fix: remove obsolete snapshots
59df67c0f9b9d26c74e2cca7d333f5868b0b202d fix: signUp test
7656f179df95b4cd96b37afbc88a1c3a2944fdf4 fix: use proper act pattern on createPassword
85246fbf1f9ab49ad3a09c11ab79bf537059b548 fix: use correct act() pattern in createUsername.spec.tsx
d5239373a2d1bbed0bfe8c1ca62ef6a70ef5c7d9 fix: the correct way to use act on form submit :-)
d84ecd168354f4acb422a5ddb725fb8faf9c3184 fix: moderation test
d8df62ab1a6486144684ff917c47e6e375ffbe03 fix: reportComment test
2756e3184bb292168e8d34e201f340c3799941e6 fix: auth tests
a28695dbdd313a7bc3dade9ac1f92d6ef0061526 Properly handle final-form actions in tests
2fafc8ea3458c5b15b66f3d65f0947672dd1a516 Update snapshots now that final form isn't overwriting props
1f9bbaec8678a7653124898ba4a2e84ddc1ef243 fix: prevent final form from overwriting field props
f6c66c003d1917db2dcb3f757e8a303266c381fa fix: prevent final form fields overwrite out props
48d1fc7318ee4ba7bf72839127e9a0b1487c1729 chore: rename translation string
728373da5728a4e7c039bd0c3a3cf0037e9f5177 fix: purge relay metadata from request
7cdea925087a6b9b6e318bbb1b31b798be87dc2f fix: radiobuttons
9735bae79222219a81a28d458976a596201b650b chore: revert obsolete checks
0b556e1693584430a5814e81d87b0f233efd1a30 fix: refactor admin configure
b245afc7b196035bcb454e031c966e63c77ce522 fix: implement withForm HOC
5787400051211f5d2e1773d7207f32b66b02a2a1 Update the Configure page form state to properly load form values
8c2af3e22a96a3d2e50e7f06fb45d1fb79cf0c8e Replace form.reset() with setTimeout(form.reset)
27d9c90e3f0166cc2db45db461619be15a3cb950 Update radio buttons and on/off fields to work with final-form updates
b852dd14af85b14ff8e0d2823e1e83bf278b29b9 Replace any on OnSubmit with typed form submission interfaces
f049a70aaf4872825ac3b2aa62dc5cb7f945290a (f) Preliminarily get Coral compiling with latest final-form libraries

* feat: act is now actAndReturn

* fix: print original filename and line number in custom console impl

* feat: trace process warnings

* fix: server warnings about potentially memory leak with too many listeners
This commit is contained in:
Vinh
2019-12-03 01:07:50 +07:00
committed by GitHub
parent 4ee8cf7c96
commit bc27d7fbec
239 changed files with 4996 additions and 10266 deletions
+2
View File
@@ -102,6 +102,8 @@ let typescriptTypeCheckingOverrides = {
},
},
}],
// 28.11.19: (cvle) Disabled because behavior of regexp.exec seems different than str.match?
"@typescript-eslint/prefer-regexp-exec": "off",
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/unbound-method": "off", // 10.10.19: (cvle) seems to give false positive.
+1
View File
@@ -6,6 +6,7 @@ module.exports = {
roots: ["<rootDir>/src"],
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
coveragePathIgnorePatterns: ["/node_modules/"],
setupFilesAfterEnv: ["<rootDir>/src/core/server/test/setupTestFramework.ts"],
testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"],
testPathIgnorePatterns: ["/node_modules/", "/client/"],
testEnvironment: "node",
+38 -8
View File
@@ -4342,6 +4342,12 @@
"@types/node": "*"
}
},
"@types/stack-trace": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz",
"integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==",
"dev": true
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -14549,12 +14555,12 @@
}
},
"final-form": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.11.0.tgz",
"integrity": "sha512-uO0VhAEbWvV8LHBpBHoIpbYqonYBXyd8gpTYFEAPCHEy5tcNqdVX4EnWQoxKl//+U9Vk0kTUsnPOWiBP2PiOUg==",
"version": "4.18.6",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.18.6.tgz",
"integrity": "sha512-LssWTXTGkoNv5WxowFTTUyC86oLYlEHH2glARBxOq8RpjiQdOyUyVG7qfPaCeOwoOw7Y5wrhSWdePyOydqSrCw==",
"dev": true,
"requires": {
"@babel/runtime": "^7.1.5"
"@babel/runtime": "^7.3.1"
}
},
"finalhandler": {
@@ -29389,12 +29395,30 @@
"dev": true
},
"react-final-form": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-4.0.2.tgz",
"integrity": "sha512-EdFWrT8nMWu5sHViuZ/VmlaYT+mLu/q5TMDWZZj5gnssUAWfgcila0QpptFNDg6+qNtepGgFh5ZPASlivoEXUA==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.3.0.tgz",
"integrity": "sha512-jijhXR1fFGUBQwNOSqF4MK8XJO7Ynl1p8vcFsnQS0INSkGI52+4IagjUgtHj3w8EviIHPFK/Eflji6FELUl07w==",
"dev": true,
"requires": {
"@babel/runtime": "^7.1.2"
"@babel/runtime": "^7.4.5",
"ts-essentials": "^2.0.8"
},
"dependencies": {
"@babel/runtime": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz",
"integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
"dev": true
}
}
},
"react-helmet": {
@@ -33398,6 +33422,12 @@
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
"dev": true
},
"ts-essentials": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz",
"integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==",
"dev": true
},
"ts-invariant": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz",
+7 -5
View File
@@ -48,7 +48,7 @@
"lint:scripts": "eslint 'scripts/**/*.{js,ts,tsx}'",
"lint:graphql": "graphql-schema-linter src/core/server/graph/tenant/schema/schema.graphql",
"lint-fix": "npm run lint:server -- --fix && npm run lint:client -- --fix && npm run lint:scripts -- --fix",
"test": "node scripts/test.js --env=jsdom",
"test": "node --trace-warnings scripts/test.js --env=jsdom",
"tscheck": "npm-run-all --parallel tscheck:*",
"tscheck:server": "tsc --project ./src/tsconfig.json --noEmit",
"tscheck:client": "tsc --project ./src/core/client/tsconfig.json --noEmit",
@@ -223,6 +223,7 @@
"@types/simplemde": "^1.11.7",
"@types/sinon": "^7.0.11",
"@types/source-map-support": "^0.5.0",
"@types/stack-trace": "0.0.29",
"@types/stack-utils": "^1.0.1",
"@types/throng": "^4.0.2",
"@types/uuid": "^3.4.4",
@@ -275,7 +276,7 @@
"eslint-plugin-react": "^7.15.1",
"eventemitter2": "^5.0.1",
"farce": "^0.2.6",
"final-form": "4.11.0",
"final-form": "4.18.6",
"flat": "^4.1.0",
"fluent-intl-polyfill": "^0.1.0",
"fluent-langneg": "^0.1.1",
@@ -326,7 +327,7 @@
"react-dev-utils": "^9.0.0",
"react-dom": "^16.9.0-alpha.0",
"react-error-overlay": "^5.1.6",
"react-final-form": "4.0.2",
"react-final-form": "6.3.0",
"react-popper": "^1.3.2",
"react-relay": "^4.0.0",
"react-relay-network-modern": "^4.0.4",
@@ -347,6 +348,7 @@
"simulant": "^0.2.2",
"sinon": "^7.3.2",
"sockjs-client": "^1.3.0",
"stack-trace": "0.0.10",
"strip-ansi": "^5.2.0",
"style-loader": "^0.23.1",
"subscriptions-transport-ws": "^0.9.16",
@@ -362,11 +364,11 @@
"tslint": "^5.20.0",
"typed-css-modules": "^0.4.2",
"typeface-manuale": "^0.0.71",
"typeface-nunito": "0.0.72",
"typeface-open-sans": "0.0.75",
"typeface-source-sans-pro": "^0.0.54",
"typescript": "3.3.4000",
"typescript-snapshots-plugin": "^1.6.0",
"typeface-nunito": "0.0.72",
"typeface-open-sans": "0.0.75",
"wait-for-expect": "^1.1.1",
"webpack": "^4.30.0",
"webpack-assets-manifest": "^3.1.1",
@@ -52,7 +52,7 @@ const UserDrawerNotesContainer: FunctionComponent<Props> = ({
userID: user.id,
body,
});
form.reset();
form.initialize({});
},
[user]
);
@@ -25,6 +25,11 @@ interface Props {
lastFocusableRef: RefObject<any>;
}
interface FormStateValues {
duration: any;
emailMessage: any;
}
const DURATIONS: ScaledUnit[] = [
{ original: 3600, value: "3600", unit: "hour", scaled: 1 }, // 1 hour
{ original: 10800, value: "10800", unit: "hour", scaled: 3 }, // 3 hours
@@ -69,7 +74,8 @@ const SuspendForm: FunctionComponent<Props> = ({
const setMessageValue: Mutator = useCallback(
([name, newValue], state, { changeValue }) => {
if (state.lastFormState) {
const { duration, emailMessage } = state.lastFormState.values;
const { duration, emailMessage } = state.lastFormState
.values as FormStateValues;
const unit = DURATIONS.find(d => d.value === duration);
const expectedEmailMessage = getMessageWithDuration(unit!);
if (expectedEmailMessage === emailMessage) {
@@ -86,7 +92,8 @@ const SuspendForm: FunctionComponent<Props> = ({
{ changeValue }
) => {
if (state.lastFormState && !checked) {
const { duration, emailMessage } = state.lastFormState.values;
const { duration, emailMessage } = state.lastFormState
.values as FormStateValues;
const unit = DURATIONS.find(d => d.value === duration);
const expectedEmailMessage = getMessageWithDuration(unit!);
if (expectedEmailMessage !== emailMessage) {
@@ -132,8 +139,8 @@ const SuspendForm: FunctionComponent<Props> = ({
$unit={unit}
>
<RadioButton
id={`duration-${value}`}
{...input}
id={`duration-${value}`}
onChange={event => {
form.mutators.setMessageValue(
"emailMessage",
@@ -156,8 +163,8 @@ const SuspendForm: FunctionComponent<Props> = ({
{({ input }) => (
<Localized id="community-suspendModal-customize">
<CheckBox
id="suspendModal-editMessage"
{...input}
id="suspendModal-editMessage"
onChange={event => {
form.mutators.resetMessageValue(
"emailMessage",
@@ -20,11 +20,11 @@ const EmailField: FunctionComponent<Props> = ({ index, disabled }) => (
<Label htmlFor={input.name}>Email Address:</Label>
</Localized>
<TextField
{...input}
data-testid={`invite-users-email.${index}`}
color={colorFromMeta(meta)}
disabled={disabled}
fullWidth
{...input}
/>
<ValidationMessage meta={meta} fullWidth />
</FormField>
@@ -21,9 +21,9 @@ const RoleField: FunctionComponent<Props> = ({ disabled }) => (
{({ input }) => (
<Localized id="role-staff">
<RadioButton
{...input}
id={`${input.name}-staff`}
disabled={disabled}
{...input}
>
Staff
</RadioButton>
@@ -34,9 +34,9 @@ const RoleField: FunctionComponent<Props> = ({ disabled }) => (
{({ input }) => (
<Localized id="role-moderator">
<RadioButton
{...input}
id={`${input.name}-moderator`}
disabled={disabled}
{...input}
>
Moderator
</RadioButton>
@@ -47,9 +47,9 @@ const RoleField: FunctionComponent<Props> = ({ disabled }) => (
{({ input }) => (
<Localized id="role-admin">
<RadioButton
{...input}
id={`${input.name}-admin`}
disabled={disabled}
{...input}
>
Admin
</RadioButton>
@@ -60,12 +60,12 @@ const UserTableFilter: FunctionComponent<Props> = props => (
attrs={{ placeholder: true, "aria-label": true }}
>
<TextField
{...input}
className={styles.textField}
color="dark"
placeholder="Search by username or email address..."
aria-label="Search by username or email address"
variant="seamlessAdornment"
{...input}
adornment={
<Localized
id="community-filter-searchButton"
@@ -14,7 +14,7 @@ import SideBar from "./SideBar";
interface Props {
onSubmit: (settings: any, form: FormApi) => void;
onChange: (formState: FormState) => void;
onChange: (formState: FormState<any>) => void;
children: React.ReactElement;
}
@@ -25,7 +25,7 @@ const Configure: FunctionComponent<Props> = ({
}) => (
<MainLayout data-testid="configure-container">
<Form onSubmit={onSubmit}>
{({ handleSubmit, submitting, pristine, form, submitError }) => (
{({ handleSubmit, submitting, form, pristine, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit} id="configure-form">
<FormSpy onChange={onChange} />
<Layout>
@@ -36,7 +36,7 @@ class ConfigureRoute extends React.Component<Props, State> {
form.initialize(data);
};
private handleChange = ({ dirty }: FormState) => {
private handleChange = ({ dirty }: FormState<any>) => {
if (dirty !== this.state.dirty) {
this.setState({ dirty });
}
@@ -2,7 +2,7 @@ import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { parseStringBool } from "coral-framework/lib/form";
import { formatBool, parseStringBool } from "coral-framework/lib/form";
import { Validator } from "coral-framework/lib/validation";
import { RadioButton } from "coral-ui/components/v2";
@@ -13,8 +13,8 @@ interface Props {
invert?: boolean;
onLabel?: React.ReactNode;
offLabel?: React.ReactNode;
format?: ((value: any, name: string) => any) | null;
parse?: ((value: any, name: string) => any) | null;
format?: (value: any, name: string) => any;
parse?: (value: any, name: string) => any;
className?: string;
}
@@ -25,19 +25,19 @@ const OnOffField: FunctionComponent<Props> = ({
offLabel,
invert = false,
parse = parseStringBool,
format,
format = formatBool,
className,
}) => (
<div className={className}>
<Field
name={name}
type="radio"
value={!invert}
value={JSON.stringify(!invert)}
parse={parse}
format={format}
>
{({ input }) => (
<RadioButton id={`${input.name}-true`} disabled={disabled} {...input}>
<RadioButton {...input} id={`${input.name}-true`} disabled={disabled}>
{onLabel || (
<Localized id="configure-onOffField-on">
<span>On</span>
@@ -51,10 +51,10 @@ const OnOffField: FunctionComponent<Props> = ({
type="radio"
parse={parse}
format={format}
value={invert}
value={JSON.stringify(invert)}
>
{({ input }) => (
<RadioButton id={`${input.name}-false`} disabled={disabled} {...input}>
<RadioButton {...input} id={`${input.name}-false`} disabled={disabled}>
{offLabel || (
<Localized id="configure-onOffField-off">
<span>Off</span>
@@ -1,51 +0,0 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { parseStringBool } from "coral-framework/lib/form";
import { Validator } from "coral-framework/lib/validation";
import { RadioButton } from "coral-ui/components/v2";
interface Props {
validate?: Validator;
name: string;
disabled: boolean;
invert?: boolean;
}
const PermissionField: FunctionComponent<Props> = ({
name,
disabled,
invert = false,
}) => (
<div>
<Field name={name} type="radio" parse={parseStringBool} value={!invert}>
{({ input }) => (
<Localized id="configure-permissionField-allow">
<RadioButton
id={`${input.name}-allow`}
disabled={disabled}
{...input}
>
Allow
</RadioButton>
</Localized>
)}
</Field>
<Field name={name} type="radio" parse={parseStringBool} value={invert}>
{({ input }) => (
<Localized id="configure-permissionField-dontAllow">
<RadioButton
id={`${input.name}-dontAllow`}
disabled={disabled}
{...input}
>
Don't allow
</RadioButton>
</Localized>
)}
</Field>
</div>
);
export default PermissionField;
@@ -1,54 +0,0 @@
import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components/v2";
import CommentStreamLiveUpdatesContainer from "./CommentStreamLiveUpdatesContainer";
import CustomCSSConfigContainer from "./CustomCSSConfigContainer";
import EmbedCodeContainer from "./EmbedCodeContainer";
import PermittedDomainsConfigContainer from "./PermittedDomainsConfigContainer";
import StoryCreationConfigContainer from "./StoryCreationConfigContainer";
interface Props {
disabled: boolean;
settings: PropTypesOf<typeof CustomCSSConfigContainer>["settings"] &
PropTypesOf<typeof PermittedDomainsConfigContainer>["settings"] &
PropTypesOf<typeof CommentStreamLiveUpdatesContainer>["settings"] &
PropTypesOf<typeof CommentStreamLiveUpdatesContainer>["settingsReadOnly"] &
PropTypesOf<typeof EmbedCodeContainer>["settings"] &
PropTypesOf<typeof StoryCreationConfigContainer>["settings"];
onInitValues: (values: any) => void;
}
const AdvancedConfig: FunctionComponent<Props> = ({
disabled,
settings,
onInitValues,
}) => (
<HorizontalGutter size="double" data-testid="configure-advancedContainer">
<EmbedCodeContainer settings={settings} />
<CustomCSSConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<CommentStreamLiveUpdatesContainer
disabled={disabled}
settings={settings}
settingsReadOnly={settings}
onInitValues={onInitValues}
/>
<PermittedDomainsConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<StoryCreationConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
</HorizontalGutter>
);
export default AdvancedConfig;
@@ -1,57 +1,56 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import React from "react";
import React, { useMemo } from "react";
import { useForm } from "react-final-form";
import { graphql } from "react-relay";
import { pureMerge } from "coral-common/utils";
import { withFragmentContainer } from "coral-framework/lib/relay";
import {
purgeMetadata,
withFragmentContainer,
} from "coral-framework/lib/relay";
import { HorizontalGutter } from "coral-ui/components";
import { AdvancedConfigContainer_settings } from "coral-admin/__generated__/AdvancedConfigContainer_settings.graphql";
import AdvancedConfig from "./AdvancedConfig";
import CommentStreamLiveUpdatesContainer from "./CommentStreamLiveUpdatesContainer";
import CustomCSSConfig from "./CustomCSSConfig";
import EmbedCodeContainer from "./EmbedCodeContainer";
import PermittedDomainsConfig from "./PermittedDomainsConfig";
import StoryCreationConfig from "./StoryCreationConfig";
interface Props {
form: FormApi;
submitting: boolean;
settings: AdvancedConfigContainer_settings;
}
class AdvancedConfigContainer extends React.Component<Props> {
public static routeConfig: RouteProps;
private initialValues = {};
constructor(props: Props) {
super(props);
}
public componentDidMount() {
this.props.form.initialize(this.initialValues);
}
private handleOnInitValues = (values: any) => {
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
return (
<AdvancedConfig
disabled={this.props.submitting}
settings={this.props.settings}
onInitValues={this.handleOnInitValues}
const AdvancedConfigContainer: React.FunctionComponent<Props> = ({
settings,
submitting,
}) => {
const form = useForm();
useMemo(() => form.initialize(purgeMetadata(settings)), []);
return (
<HorizontalGutter size="double" data-testid="configure-advancedContainer">
<EmbedCodeContainer settings={settings} />
<CustomCSSConfig disabled={submitting} />
<CommentStreamLiveUpdatesContainer
disabled={submitting}
settings={settings}
/>
);
}
}
<PermittedDomainsConfig disabled={submitting} />
<StoryCreationConfig disabled={submitting} />
</HorizontalGutter>
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment AdvancedConfigContainer_settings on Settings {
...CustomCSSConfig_formValues @relay(mask: false)
...PermittedDomainsConfig_formValues @relay(mask: false)
...CommentStreamLiveUpdates_formValues @relay(mask: false)
...StoryCreationConfig_formValues @relay(mask: false)
...EmbedCodeContainer_settings
...CustomCSSConfigContainer_settings
...PermittedDomainsConfigContainer_settings
...CommentStreamLiveUpdatesContainer_settings
...CommentStreamLiveUpdatesContainer_settingsReadOnly
...StoryCreationConfigContainer_settings
}
`,
})(AdvancedConfigContainer);
@@ -1,4 +1,3 @@
import { FormApi } from "final-form";
import React from "react";
import { graphql } from "react-relay";
@@ -11,7 +10,6 @@ import AdvancedConfigContainer from "./AdvancedConfigContainer";
interface Props {
data: AdvancedConfigRouteQueryResponse | null;
form: FormApi;
submitting: boolean;
}
@@ -27,7 +25,6 @@ class AdvancedConfigRoute extends React.Component<Props> {
return (
<AdvancedConfigContainer
settings={this.props.data.settings}
form={this.props.form}
submitting={this.props.submitting}
/>
);
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import { FormField, FormFieldDescription } from "coral-ui/components/v2";
@@ -7,6 +8,15 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import OnOffField from "../../OnOffField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment CommentStreamLiveUpdates_formValues on Settings {
live {
enabled
}
}
`;
interface Props {
disabled: boolean;
}
@@ -4,49 +4,30 @@ import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { CommentStreamLiveUpdatesContainer_settings } from "coral-admin/__generated__/CommentStreamLiveUpdatesContainer_settings.graphql";
import { CommentStreamLiveUpdatesContainer_settingsReadOnly } from "coral-admin/__generated__/CommentStreamLiveUpdatesContainer_settingsReadOnly.graphql";
import CommentStreamLiveUpdates from "./CommentStreamLiveUpdates";
interface Props {
settingsReadOnly: CommentStreamLiveUpdatesContainer_settingsReadOnly;
settings: CommentStreamLiveUpdatesContainer_settings;
onInitValues: (values: CommentStreamLiveUpdatesContainer_settings) => void;
disabled: boolean;
}
class CommentStreamLiveUpdatesContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
const CommentStreamLiveUpdatesContainer: React.FunctionComponent<Props> = ({
disabled,
settings: {
live: { configurable },
},
}) => {
if (!configurable) {
return null;
}
public render() {
const {
disabled,
settingsReadOnly: {
live: { configurable },
},
} = this.props;
if (!configurable) {
return null;
}
return <CommentStreamLiveUpdates disabled={disabled} />;
}
}
return <CommentStreamLiveUpdates disabled={disabled} />;
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment CommentStreamLiveUpdatesContainer_settings on Settings {
live {
enabled
}
}
`,
settingsReadOnly: graphql`
fragment CommentStreamLiveUpdatesContainer_settingsReadOnly on Settings {
live {
configurable
}
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { formatEmpty, parseEmptyAsNull } from "coral-framework/lib/form";
import { FormField, FormFieldDescription } from "coral-ui/components/v2";
@@ -9,6 +10,13 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import TextFieldWithValidation from "../../TextFieldWithValidation";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment CustomCSSConfig_formValues on Settings {
customCSSURL
}
`;
interface Props {
disabled: boolean;
}
@@ -34,6 +42,7 @@ const CustomCSSConfig: FunctionComponent<Props> = ({ disabled }) => (
<Field name="customCSSURL" parse={parseEmptyAsNull} format={formatEmpty}>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={`configure-advanced-${input.name}`}
disabled={disabled}
autoComplete="off"
@@ -42,7 +51,6 @@ const CustomCSSConfig: FunctionComponent<Props> = ({ disabled }) => (
spellCheck={false}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
@@ -1,36 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { CustomCSSConfigContainer_settings as SettingsData } from "coral-admin/__generated__/CustomCSSConfigContainer_settings.graphql";
import CustomCSSConfig from "./CustomCSSConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class CustomCSSConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <CustomCSSConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment CustomCSSConfigContainer_settings on Settings {
customCSSURL
}
`,
})(CustomCSSConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { formatStringList, parseStringList } from "coral-framework/lib/form";
import { validateStrictURLList } from "coral-framework/lib/validation";
@@ -10,6 +11,13 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import TextFieldWithValidation from "../../TextFieldWithValidation";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment PermittedDomainsConfig_formValues on Settings {
allowedDomains
}
`;
interface Props {
disabled: boolean;
}
@@ -44,6 +52,7 @@ const PermittedDomainsConfig: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={`configure-advanced-${input.name}`}
disabled={disabled}
autoComplete="off"
@@ -51,7 +60,6 @@ const PermittedDomainsConfig: FunctionComponent<Props> = ({ disabled }) => (
autoCapitalize="off"
spellCheck={false}
meta={meta}
{...input}
fullWidth
/>
)}
@@ -1,36 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { PermittedDomainsConfigContainer_settings as SettingsData } from "coral-admin/__generated__/PermittedDomainsConfigContainer_settings.graphql";
import PermittedDomainsConfig from "./PermittedDomainsConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class PermittedDomainsConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <PermittedDomainsConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment PermittedDomainsConfigContainer_settings on Settings {
allowedDomains
}
`,
})(PermittedDomainsConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { parseEmptyAsNull } from "coral-framework/lib/form";
import { ExternalLink } from "coral-framework/lib/i18n/components";
@@ -18,6 +19,19 @@ import HelperText from "../../HelperText";
import OnOffField from "../../OnOffField";
import TextFieldWithValidation from "../../TextFieldWithValidation";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment StoryCreationConfig_formValues on Settings {
stories {
scraping {
enabled
proxyURL
}
disableLazy
}
}
`;
interface Props {
disabled: boolean;
}
@@ -90,6 +104,7 @@ const StoryCreationConfig: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id="configure-advanced-stories-proxy-url"
disabled={disabled}
fullWidth
@@ -98,7 +113,6 @@ const StoryCreationConfig: FunctionComponent<Props> = ({ disabled }) => (
autoCapitalize="off"
spellCheck={false}
meta={meta}
{...input}
/>
)}
</Field>
@@ -1,42 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { StoryCreationConfigContainer_settings as SettingsData } from "coral-admin/__generated__/StoryCreationConfigContainer_settings.graphql";
import StoryCreationConfig from "./StoryCreationConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class StoryCreationConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <StoryCreationConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment StoryCreationConfigContainer_settings on Settings {
stories {
scraping {
enabled
proxyURL
}
disableLazy
}
}
`,
})(StoryCreationConfigContainer);
export default enhanced;
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import {
Flex,
@@ -16,6 +17,17 @@ import OnOffField from "../../OnOffField";
import styles from "./AccountFeaturesConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment AccountFeaturesConfig_formValues on Settings {
accountFeatures {
changeUsername
deleteAccount
downloadComments
}
}
`;
interface Props {
disabled?: boolean;
}
@@ -1,45 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { DeepNullable, DeepPartial } from "coral-common/types";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { GQLSettings } from "coral-framework/schema";
import { AccountFeaturesConfigContainer_settings as SettingsData } from "coral-admin/__generated__/AccountFeaturesConfigContainer_settings.graphql";
import AccountFeaturesConfig from "./AccountFeaturesConfig";
export type FormProps = DeepNullable<Pick<GQLSettings, "accountFeatures">>;
export type OnInitValuesFct = (values: DeepPartial<FormProps>) => void;
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled?: boolean;
}
class AccountFeaturesConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <AccountFeaturesConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment AccountFeaturesConfigContainer_settings on Settings {
accountFeatures {
changeUsername
deleteAccount
downloadComments
}
}
`,
})(AccountFeaturesConfigContainer);
export default enhanced;
@@ -1,36 +0,0 @@
import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components/v2";
import { OnInitValuesFct } from "./AuthConfigContainer";
import AuthIntegrationsConfig from "./AuthIntegrationsConfig";
import SessionConfigContainer from "./SessionConfigContainer";
interface Props {
disabled?: boolean;
auth: PropTypesOf<typeof AuthIntegrationsConfig>["auth"] &
PropTypesOf<typeof SessionConfigContainer>["auth"];
onInitValues: OnInitValuesFct;
}
const AuthConfig: FunctionComponent<Props> = ({
disabled,
auth,
onInitValues,
}) => (
<HorizontalGutter size="double" data-testid="configure-authContainer">
<SessionConfigContainer
auth={auth}
disabled={disabled}
onInitValues={onInitValues}
/>
<AuthIntegrationsConfig
disabled={disabled}
auth={auth}
onInitValues={onInitValues}
/>
</HorizontalGutter>
);
export default AuthConfig;
@@ -5,24 +5,28 @@ import React from "react";
import { graphql } from "react-relay";
import { DeepNullable, DeepPartial } from "coral-common/types";
import { pureMerge } from "coral-common/utils";
import { CoralContext, withContext } from "coral-framework/lib/bootstrap";
import {
AddSubmitHook,
RemoveSubmitHook,
SubmitHook,
withForm,
withSubmitHookContext,
} from "coral-framework/lib/form";
import { getMessage } from "coral-framework/lib/i18n";
import { withFragmentContainer } from "coral-framework/lib/relay";
import {
purgeMetadata,
withFragmentContainer,
} from "coral-framework/lib/relay";
import { GQLSettings } from "coral-framework/schema";
import { HorizontalGutter } from "coral-ui/components/v2";
import { AuthConfigContainer_auth as AuthData } from "coral-admin/__generated__/AuthConfigContainer_auth.graphql";
import { AuthConfigContainer_settings as SettingsData } from "coral-admin/__generated__/AuthConfigContainer_settings.graphql";
import AccountFeaturesConfigContainer from "./AccountFeaturesConfigContainer";
import AuthConfig from "./AuthConfig";
import AccountFeaturesConfig from "./AccountFeaturesConfig";
import AuthIntegrationsConfig from "./AuthIntegrationsConfig";
import SessionConfig from "./SessionConfig";
export type FormProps = DeepNullable<
Pick<GQLSettings, "auth" | "accountFeatures">
@@ -40,16 +44,17 @@ interface Props {
class AuthConfigContainer extends React.Component<Props> {
public static routeConfig: RouteProps;
private initialValues: DeepPartial<FormProps> = {};
private removeSubmitHook: RemoveSubmitHook;
constructor(props: Props) {
super(props);
this.removeSubmitHook = this.props.addSubmitHook(this.submitHook);
}
public componentDidMount() {
this.props.form.initialize(this.initialValues);
this.props.form.initialize(
purgeMetadata({
...props.settings,
auth: props.auth,
})
);
}
public componentWillUnmount() {
@@ -97,51 +102,48 @@ class AuthConfigContainer extends React.Component<Props> {
return;
};
private handleOnInitValues: OnInitValuesFct = values => {
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
return (
<HorizontalGutter size="double">
<AccountFeaturesConfigContainer
onInitValues={this.handleOnInitValues}
settings={this.props.settings}
disabled={this.props.submitting}
/>
<AuthConfig
disabled={this.props.submitting}
<HorizontalGutter size="double" data-testid="configure-authContainer">
<AccountFeaturesConfig disabled={this.props.submitting} />
<SessionConfig disabled={this.props.submitting} />
<AuthIntegrationsConfig
auth={this.props.auth}
onInitValues={this.handleOnInitValues}
disabled={this.props.submitting}
/>
</HorizontalGutter>
);
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment AuthConfigContainer_settings on Settings {
...AccountFeaturesConfigContainer_settings
}
`,
auth: graphql`
fragment AuthConfigContainer_auth on Auth {
...FacebookConfigContainer_auth
...FacebookConfigContainer_authReadOnly
...GoogleConfigContainer_auth
...GoogleConfigContainer_authReadOnly
...SSOConfigContainer_auth
...SSOConfigContainer_authReadOnly
...LocalAuthConfigContainer_auth
...OIDCConfigContainer_auth
...OIDCConfigContainer_authReadOnly
...SessionConfigContainer_auth
}
`,
})(
withSubmitHookContext(addSubmitHook => ({ addSubmitHook }))(
withContext(({ localeBundles }) => ({ localeBundles }))(AuthConfigContainer)
const enhanced = withForm(
withFragmentContainer<Props>({
settings: graphql`
fragment AuthConfigContainer_settings on Settings {
...AccountFeaturesConfig_formValues @relay(mask: false)
}
`,
auth: graphql`
fragment AuthConfigContainer_auth on Auth {
...FacebookConfig_formValues @relay(mask: false)
...GoogleConfig_formValues @relay(mask: false)
...SSOConfig_formValues @relay(mask: false)
...LocalAuthConfig_formValues @relay(mask: false)
...OIDCConfig_formValues @relay(mask: false)
...SessionConfig_formValues @relay(mask: false)
...FacebookConfigContainer_auth
...GoogleConfigContainer_auth
...SSOConfigContainer_auth
...OIDCConfigContainer_auth
}
`,
})(
withSubmitHookContext(addSubmitHook => ({ addSubmitHook }))(
withContext(({ localeBundles }) => ({ localeBundles }))(
AuthConfigContainer
)
)
)
);
@@ -1,4 +1,3 @@
import { FormApi } from "final-form";
import React from "react";
import { graphql } from "react-relay";
@@ -11,7 +10,6 @@ import AuthConfigContainer from "./AuthConfigContainer";
interface Props {
data: AuthConfigRouteContainerQueryResponse | null;
form: FormApi;
submitting?: boolean;
}
@@ -28,7 +26,6 @@ class AuthConfigRoute extends React.Component<Props> {
<AuthConfigContainer
settings={this.props.data.settings}
auth={this.props.data.settings.auth}
form={this.props.form}
submitting={this.props.submitting}
/>
);
@@ -42,7 +39,6 @@ const enhanced = withRouteConfig<Props>({
...AuthConfigContainer_settings
auth {
...AuthConfigContainer_auth
...SessionConfigContainer_auth
}
}
}
@@ -3,62 +3,30 @@ import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components/v2";
import { OnInitValuesFct } from "./AuthConfigContainer";
import FacebookConfigContainer from "./FacebookConfigContainer";
import GoogleConfigContainer from "./GoogleConfigContainer";
import LocalAuthConfigContainer from "./LocalAuthConfigContainer";
import LocalAuthConfig from "./LocalAuthConfig";
import OIDCConfigContainer from "./OIDCConfigContainer";
import SSOConfigContainer from "./SSOConfigContainer";
interface Props {
disabled?: boolean;
auth: PropTypesOf<typeof FacebookConfigContainer>["auth"] &
PropTypesOf<typeof FacebookConfigContainer>["authReadOnly"] &
PropTypesOf<typeof GoogleConfigContainer>["auth"] &
PropTypesOf<typeof GoogleConfigContainer>["authReadOnly"] &
PropTypesOf<typeof SSOConfigContainer>["auth"] &
PropTypesOf<typeof SSOConfigContainer>["authReadOnly"] &
PropTypesOf<typeof LocalAuthConfigContainer>["auth"] &
PropTypesOf<typeof OIDCConfigContainer>["auth"] &
PropTypesOf<typeof OIDCConfigContainer>["authReadOnly"];
onInitValues: OnInitValuesFct;
PropTypesOf<typeof OIDCConfigContainer>["auth"];
}
const AuthIntegrationsConfig: FunctionComponent<Props> = ({
disabled,
auth,
onInitValues,
}) => (
<HorizontalGutter size="double">
<LocalAuthConfigContainer
disabled={disabled}
auth={auth}
onInitValues={onInitValues}
/>
<OIDCConfigContainer
disabled={disabled}
auth={auth}
authReadOnly={auth}
onInitValues={onInitValues}
/>
<SSOConfigContainer
disabled={disabled}
auth={auth}
authReadOnly={auth}
onInitValues={onInitValues}
/>
<GoogleConfigContainer
disabled={disabled}
auth={auth}
authReadOnly={auth}
onInitValues={onInitValues}
/>
<FacebookConfigContainer
disabled={disabled}
auth={auth}
authReadOnly={auth}
onInitValues={onInitValues}
/>
<LocalAuthConfig disabled={disabled} />
<OIDCConfigContainer disabled={disabled} auth={auth} />
<SSOConfigContainer disabled={disabled} auth={auth} />
<GoogleConfigContainer disabled={disabled} auth={auth} />
<FacebookConfigContainer disabled={disabled} auth={auth} />
</HorizontalGutter>
);
@@ -20,21 +20,24 @@ const ClientSecretField: FunctionComponent<Props> = ({
validate,
}) => (
<FormField>
<Localized id="configure-auth-clientID">
<Label>Client ID</Label>
</Localized>
<Field name={name} parse={parseEmptyAsNull} validate={validate}>
{({ input, meta }) => (
<TextFieldWithValidation
disabled={disabled}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
meta={meta}
{...input}
spellCheck={false}
fullWidth
/>
<>
<Localized id="configure-auth-clientID">
<Label htmlFor={input.name}>Client ID</Label>
</Localized>
<TextFieldWithValidation
{...input}
id={input.name}
disabled={disabled}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
meta={meta}
spellCheck={false}
fullWidth
/>
</>
)}
</Field>
</FormField>
@@ -18,9 +18,6 @@ const ClientSecretField: FunctionComponent<Props> = ({
validate,
}) => (
<FormField>
<Localized id="configure-auth-clientSecret">
<Label>Client secret</Label>
</Localized>
<Field
name={name}
key={(disabled && "on") || "off"}
@@ -29,13 +26,17 @@ const ClientSecretField: FunctionComponent<Props> = ({
>
{({ input, meta }) => (
<>
<Localized id="configure-auth-clientSecret">
<Label htmlFor={input.name}>Client secret</Label>
</Localized>
<PasswordField
{...input}
id={input.name}
disabled={disabled || meta.submitting}
// TODO: (wyattjoh) figure out how to add translations to these props
hidePasswordTitle="Show Client Secret"
showPasswordTitle="Hide Client Secret"
fullWidth
{...input}
/>
<ValidationMessage meta={meta} fullWidth />
</>
@@ -32,7 +32,7 @@ const ConfigBoxWithToggleField: FunctionComponent<Props> = ({
topRight={
<FormField>
<Localized id="configure-auth-configBoxEnabled">
<CheckBox id={input.name} disabled={disabled} light {...input}>
<CheckBox {...input} id={input.name} disabled={disabled} light>
Enabled
</CheckBox>
</Localized>
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import {
Condition,
@@ -17,6 +18,24 @@ import RedirectField from "./RedirectField";
import RegistrationField from "./RegistrationField";
import TargetFilterField from "./TargetFilterField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment FacebookConfig_formValues on Auth {
integrations {
facebook {
enabled
allowRegistration
targetFilter {
admin
stream
}
clientID
clientSecret
}
}
}
`;
interface Props {
disabled?: boolean;
callbackURL: string;
@@ -29,7 +48,12 @@ const FacebookLink = () => (
);
const isEnabled: Condition = (value, values) =>
Boolean(values.auth.integrations.facebook.enabled);
Boolean(
values.auth &&
values.auth.integrations &&
values.auth.integrations.facebook &&
values.auth.integrations.facebook.enabled
);
const FacebookConfig: FunctionComponent<Props> = ({
disabled,
@@ -4,54 +4,29 @@ import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { FacebookConfigContainer_auth as AuthData } from "coral-admin/__generated__/FacebookConfigContainer_auth.graphql";
import { FacebookConfigContainer_authReadOnly as AuthReadOnlyData } from "coral-admin/__generated__/FacebookConfigContainer_authReadOnly.graphql";
import { OnInitValuesFct } from "./AuthConfigContainer";
import FacebookConfig from "./FacebookConfig";
interface Props {
auth: AuthData;
authReadOnly: AuthReadOnlyData;
onInitValues: OnInitValuesFct;
disabled?: boolean;
}
class FacebookConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ auth: props.auth });
}
public render() {
const { disabled, authReadOnly } = this.props;
return (
<FacebookConfig
disabled={disabled}
callbackURL={authReadOnly.integrations.facebook.callbackURL}
/>
);
}
}
const FacebookConfigContainer: React.FunctionComponent<Props> = ({
disabled,
auth,
}) => {
return (
<FacebookConfig
disabled={disabled}
callbackURL={auth.integrations.facebook.callbackURL}
/>
);
};
const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment FacebookConfigContainer_auth on Auth {
integrations {
facebook {
enabled
allowRegistration
targetFilter {
admin
stream
}
clientID
clientSecret
}
}
}
`,
authReadOnly: graphql`
fragment FacebookConfigContainer_authReadOnly on Auth {
integrations {
facebook {
callbackURL
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import {
Condition,
@@ -17,6 +18,23 @@ import RedirectField from "./RedirectField";
import RegistrationField from "./RegistrationField";
import TargetFilterField from "./TargetFilterField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment GoogleConfig_formValues on Auth {
integrations {
google {
enabled
allowRegistration
targetFilter {
admin
stream
}
clientID
clientSecret
}
}
}
`;
interface Props {
disabled?: boolean;
callbackURL: string;
@@ -42,6 +60,7 @@ const GoogleConfig: FunctionComponent<Props> = ({ disabled, callbackURL }) => (
}
name="auth.integrations.google.enabled"
disabled={disabled}
data-testid="configure-auth-google"
>
{disabledInside => (
<>
@@ -4,54 +4,29 @@ import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { GoogleConfigContainer_auth as AuthData } from "coral-admin/__generated__/GoogleConfigContainer_auth.graphql";
import { GoogleConfigContainer_authReadOnly as AuthReadOnlyData } from "coral-admin/__generated__/GoogleConfigContainer_authReadOnly.graphql";
import { OnInitValuesFct } from "./AuthConfigContainer";
import GoogleConfig from "./GoogleConfig";
interface Props {
auth: AuthData;
authReadOnly: AuthReadOnlyData;
onInitValues: OnInitValuesFct;
disabled?: boolean;
}
class GoogleConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ auth: props.auth });
}
public render() {
const { disabled, authReadOnly } = this.props;
return (
<GoogleConfig
disabled={disabled}
callbackURL={authReadOnly.integrations.google.callbackURL}
/>
);
}
}
const GoogleConfigContainer: React.FunctionComponent<Props> = ({
disabled,
auth,
}) => {
return (
<GoogleConfig
disabled={disabled}
callbackURL={auth.integrations.google.callbackURL}
/>
);
};
const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment GoogleConfigContainer_auth on Auth {
integrations {
google {
enabled
allowRegistration
targetFilter {
admin
stream
}
clientID
clientSecret
}
}
}
`,
authReadOnly: graphql`
fragment GoogleConfigContainer_authReadOnly on Auth {
integrations {
google {
callbackURL
@@ -1,11 +1,28 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import Header from "../../Header";
import ConfigBoxWithToggleField from "./ConfigBoxWithToggleField";
import RegistrationField from "./RegistrationField";
import TargetFilterField from "./TargetFilterField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment LocalAuthConfig_formValues on Auth {
integrations {
local {
enabled
allowRegistration
targetFilter {
admin
stream
}
}
}
}
`;
interface Props {
disabled?: boolean;
}
@@ -19,6 +36,7 @@ const LocalAuthConfig: FunctionComponent<Props> = ({ disabled }) => (
}
name="auth.integrations.local.enabled"
disabled={disabled}
data-testid="configure-auth-local"
>
{disabledInside => (
<>
@@ -1,46 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { LocalAuthConfigContainer_auth as AuthData } from "coral-admin/__generated__/LocalAuthConfigContainer_auth.graphql";
import { OnInitValuesFct } from "./AuthConfigContainer";
import LocalAuthConfig from "./LocalAuthConfig";
interface Props {
auth: AuthData;
onInitValues: OnInitValuesFct;
disabled?: boolean;
}
class LocalAuthConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ auth: props.auth });
}
public render() {
const { disabled } = this.props;
return <LocalAuthConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment LocalAuthConfigContainer_auth on Auth {
integrations {
local {
enabled
allowRegistration
targetFilter {
admin
stream
}
}
}
}
`,
})(LocalAuthConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { colorFromMeta, parseEmptyAsNull } from "coral-framework/lib/form";
import {
@@ -33,6 +34,29 @@ import RedirectField from "./RedirectField";
import RegistrationField from "./RegistrationField";
import TargetFilterField from "./TargetFilterField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment OIDCConfig_formValues on Auth {
integrations {
oidc {
enabled
allowRegistration
targetFilter {
admin
stream
}
name
clientID
clientSecret
authorizationURL
tokenURL
jwksURI
issuer
}
}
}
`;
interface Props {
disabled?: boolean;
callbackURL: string;
@@ -76,7 +100,9 @@ const OIDCConfig: FunctionComponent<Props> = ({
<FormField>
<FormFieldHeader>
<Localized id="configure-auth-oidc-providerName">
<Label>Provider name</Label>
<Label htmlFor="auth.integrations.oidc.name">
Provider name
</Label>
</Localized>
<Localized id="configure-auth-oidc-providerNameDescription">
<HelperText>
@@ -94,6 +120,8 @@ const OIDCConfig: FunctionComponent<Props> = ({
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={input.name}
disabled={disabledInside}
autoComplete="off"
autoCorrect="off"
@@ -102,7 +130,6 @@ const OIDCConfig: FunctionComponent<Props> = ({
color={colorFromMeta(meta)}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
@@ -120,7 +147,7 @@ const OIDCConfig: FunctionComponent<Props> = ({
<FormField>
<FormFieldHeader>
<Localized id="configure-auth-oidc-issuer">
<Label>Issuer</Label>
<Label htmlFor="auth.integrations.oidc.issuer">Issuer</Label>
</Localized>
<Localized id="configure-auth-oidc-issuerDescription">
<HelperText>
@@ -139,6 +166,8 @@ const OIDCConfig: FunctionComponent<Props> = ({
<>
<Flex direction="row" itemGutter="half" alignItems="center">
<TextField
{...input}
id={input.name}
disabled={disabledInside || disableForDiscover}
autoComplete="off"
autoCorrect="off"
@@ -146,7 +175,6 @@ const OIDCConfig: FunctionComponent<Props> = ({
spellCheck={false}
color={colorFromMeta(meta)}
fullWidth
{...input}
/>
<Button
id="configure-auth-oidc-discover"
@@ -174,6 +202,8 @@ const OIDCConfig: FunctionComponent<Props> = ({
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={input.name}
disabled={disabledInside || disableForDiscover}
autoComplete="off"
autoCorrect="off"
@@ -181,7 +211,6 @@ const OIDCConfig: FunctionComponent<Props> = ({
spellCheck={false}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
@@ -197,6 +226,8 @@ const OIDCConfig: FunctionComponent<Props> = ({
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={input.name}
disabled={disabledInside || disableForDiscover}
autoComplete="off"
autoCorrect="off"
@@ -204,7 +235,6 @@ const OIDCConfig: FunctionComponent<Props> = ({
spellCheck={false}
meta={meta}
fullWidth
{...input}
/>
)}
</Field>
@@ -220,6 +250,8 @@ const OIDCConfig: FunctionComponent<Props> = ({
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={input.name}
disabled={disabledInside || disableForDiscover}
autoComplete="off"
autoCorrect="off"
@@ -227,7 +259,6 @@ const OIDCConfig: FunctionComponent<Props> = ({
spellCheck={false}
meta={meta}
fullWidth
{...input}
/>
)}
</Field>
@@ -1,29 +1,25 @@
import { FormApi } from "final-form";
import React from "react";
import { ReactContext, withReactFinalForm } from "react-final-form";
import { graphql } from "react-relay";
import { InferableComponentEnhancer } from "recompose";
import { withForm } from "coral-framework/lib/form";
import {
FetchProp,
withFetch,
withFragmentContainer,
} from "coral-framework/lib/relay";
import { OIDCConfig_formValues } from "coral-admin/__generated__/OIDCConfig_formValues.graphql";
import { OIDCConfigContainer_auth as AuthData } from "coral-admin/__generated__/OIDCConfigContainer_auth.graphql";
import { OIDCConfigContainer_authReadOnly as AuthReadOnlyData } from "coral-admin/__generated__/OIDCConfigContainer_authReadOnly.graphql";
import { OnInitValuesFct } from "./AuthConfigContainer";
import DiscoverOIDCConfigurationFetch from "./DiscoverOIDCConfigurationFetch";
import OIDCConfig from "./OIDCConfig";
interface Props {
auth: AuthData;
authReadOnly: AuthReadOnlyData;
onInitValues: OnInitValuesFct;
disabled?: boolean;
discoverOIDCConfiguration: FetchProp<typeof DiscoverOIDCConfigurationFetch>;
reactFinalForm: FormApi;
form: FormApi<{ auth: AuthData & OIDCConfig_formValues }>;
}
interface State {
@@ -36,22 +32,35 @@ class OIDCConfigContainer extends React.Component<Props, State> {
};
private handleDiscover = async () => {
const form = this.props.reactFinalForm;
const issuer = this.props.form.getState().values.auth.integrations.oidc
.issuer;
if (!issuer) {
return;
}
this.setState({ awaitingResponse: true });
try {
const config = await this.props.discoverOIDCConfiguration({
issuer: form.getState().values.auth.integrations.oidc.issuer,
issuer,
});
if (config) {
if (config.issuer) {
form.change("auth.integrations.oidc.issuer", config.issuer);
this.props.form.change(
"auth.integrations.oidc.issuer",
config.issuer
);
}
form.change(
this.props.form.change(
"auth.integrations.oidc.authorizationURL",
config.authorizationURL
);
form.change("auth.integrations.oidc.jwksURI", config.jwksURI);
form.change("auth.integrations.oidc.tokenURL", config.tokenURL);
this.props.form.change(
"auth.integrations.oidc.jwksURI",
config.jwksURI
);
this.props.form.change(
"auth.integrations.oidc.tokenURL",
config.tokenURL
);
}
} catch (error) {
// FIXME: (wyattjoh) handle error
@@ -61,17 +70,12 @@ class OIDCConfigContainer extends React.Component<Props, State> {
this.setState({ awaitingResponse: false });
};
constructor(props: Props) {
super(props);
props.onInitValues({ auth: props.auth });
}
public render() {
const { disabled, authReadOnly } = this.props;
const { disabled, auth } = this.props;
return (
<OIDCConfig
disabled={disabled}
callbackURL={authReadOnly.integrations.oidc.callbackURL}
callbackURL={auth.integrations.oidc.callbackURL}
onDiscover={this.handleDiscover}
disableForDiscover={this.state.awaitingResponse}
/>
@@ -79,36 +83,11 @@ class OIDCConfigContainer extends React.Component<Props, State> {
}
}
// (cvle) Fix `withReactFinalForm` typings (v4.1.0), forgive this, we'll
// probably only use hooks in the future instead anyway ;-)
const withForm = withReactFinalForm as InferableComponentEnhancer<ReactContext>;
const enhanced = withForm(
withFetch(DiscoverOIDCConfigurationFetch)(
withFragmentContainer<Props>({
auth: graphql`
fragment OIDCConfigContainer_auth on Auth {
integrations {
oidc {
enabled
allowRegistration
targetFilter {
admin
stream
}
name
clientID
clientSecret
authorizationURL
tokenURL
jwksURI
issuer
}
}
}
`,
authReadOnly: graphql`
fragment OIDCConfigContainer_authReadOnly on Auth {
integrations {
oidc {
callbackURL
@@ -32,7 +32,7 @@ const RegistrationField: FunctionComponent<Props> = ({ name, disabled }) => (
<Field name={name} type="checkbox">
{({ input }) => (
<Localized id="configure-auth-registrationCheckBox">
<CheckBox id={input.name} disabled={disabled} {...input}>
<CheckBox {...input} id={input.name} disabled={disabled}>
Allow Registration
</CheckBox>
</Localized>
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import { PropTypesOf } from "coral-framework/types";
@@ -9,6 +10,22 @@ import RegistrationField from "./RegistrationField";
import SSOKeyFieldContainer from "./SSOKeyFieldContainer";
import TargetFilterField from "./TargetFilterField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment SSOConfig_formValues on Auth {
integrations {
sso {
enabled
allowRegistration
targetFilter {
admin
stream
}
}
}
}
`;
interface Props {
disabled?: boolean;
sso: PropTypesOf<typeof SSOKeyFieldContainer>["sso"];
@@ -23,6 +40,7 @@ const SSOConfig: FunctionComponent<Props> = ({ disabled, sso }) => (
}
name="auth.integrations.sso.enabled"
disabled={disabled}
data-testid="configure-auth-sso"
>
{disabledInside => (
<>
@@ -4,52 +4,24 @@ import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { SSOConfigContainer_auth as AuthData } from "coral-admin/__generated__/SSOConfigContainer_auth.graphql";
import { SSOConfigContainer_authReadOnly as AuthReadOnlyData } from "coral-admin/__generated__/SSOConfigContainer_authReadOnly.graphql";
import { OnInitValuesFct } from "./AuthConfigContainer";
import SSOConfig from "./SSOConfig";
interface Props {
auth: AuthData;
authReadOnly: AuthReadOnlyData;
onInitValues: OnInitValuesFct;
disabled?: boolean;
}
class SSOConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ auth: props.auth });
}
public render() {
const { disabled } = this.props;
return (
<SSOConfig
disabled={disabled}
sso={this.props.authReadOnly.integrations.sso}
/>
);
}
}
const SSOConfigContainer: React.FunctionComponent<Props> = ({
disabled,
auth,
}) => {
return <SSOConfig disabled={disabled} sso={auth.integrations.sso} />;
};
const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment SSOConfigContainer_auth on Auth {
integrations {
sso {
enabled
allowRegistration
targetFilter {
admin
stream
}
}
}
}
`,
authReadOnly: graphql`
fragment SSOConfigContainer_authReadOnly on Auth {
integrations {
sso {
...SSOKeyFieldContainer_sso
@@ -27,11 +27,12 @@ const SSOKeyField: FunctionComponent<Props> = ({
disabled,
onRegenerate,
}) => (
<FormField className={styles.root} data-testid="configure-auth-sso-key">
<FormField className={styles.root}>
<Localized id="configure-auth-sso-key">
<Label>Key</Label>
<Label htmlFor="configure-auth-sso-key">Key</Label>
</Localized>
<PasswordField
id="configure-auth-sso-key"
name="key"
value={generatedKey}
readOnly
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { colorFromMeta } from "coral-framework/lib/form";
import {
@@ -19,6 +20,13 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import ValidationMessage from "../../ValidationMessage";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment SessionConfig_formValues on Auth {
sessionDuration
}
`;
interface Props {
disabled?: boolean;
}
@@ -45,9 +53,9 @@ const SessionConfig: FunctionComponent<Props> = ({ disabled }) => (
{({ input, meta }) => (
<>
<DurationField
{...input}
color={colorFromMeta(meta)}
disabled={!!disabled}
{...input}
/>
<ValidationMessage meta={meta} fullWidth />
</>
@@ -1,36 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { SessionConfigContainer_auth as AuthData } from "coral-admin/__generated__/SessionConfigContainer_auth.graphql";
import { OnInitValuesFct } from "./AuthConfigContainer";
import SessionConfig from "./SessionConfig";
interface Props {
auth: AuthData;
onInitValues: OnInitValuesFct;
disabled?: boolean;
}
class SessionConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ auth: props.auth });
}
public render() {
const { disabled } = this.props;
return <SessionConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment SessionConfigContainer_auth on Auth {
sessionDuration
}
`,
})(SessionConfigContainer);
export default enhanced;
@@ -22,7 +22,7 @@ const TargetFilterField: FunctionComponent<Props> = ({
<Field name={`${name}.admin`} type="checkbox" parse={parseBool}>
{({ input }) => (
<Localized id="configure-auth-targetFilterCoralAdmin">
<CheckBox id={input.name} disabled={disabled} {...input}>
<CheckBox {...input} id={input.name} disabled={disabled}>
Coral Admin
</CheckBox>
</Localized>
@@ -31,7 +31,7 @@ const TargetFilterField: FunctionComponent<Props> = ({
<Field name={`${name}.stream`} type="checkbox" parse={parseBool}>
{({ input }) => (
<Localized id="configure-auth-targetFilterCommentStream">
<CheckBox id={input.name} disabled={disabled} {...input}>
<CheckBox {...input} id={input.name} disabled={disabled}>
Comment Stream
</CheckBox>
</Localized>
@@ -1,108 +1,80 @@
import { FormApi } from "final-form";
import { Localized } from "fluent-react/compat";
import { RouteProps } from "found";
import React from "react";
import React, { useMemo } from "react";
import { useForm } from "react-final-form";
import { graphql } from "react-relay";
import { DeepNullable, DeepPartial } from "coral-common/types";
import { pureMerge } from "coral-common/utils";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { DeepNullable } from "coral-common/types";
import {
purgeMetadata,
withFragmentContainer,
} from "coral-framework/lib/relay";
import { GQLSettings } from "coral-framework/schema";
import { EmailConfigContainer_email } from "coral-admin/__generated__/EmailConfigContainer_email.graphql";
import Header from "../../Header";
import ConfigBoxWithToggleField from "../Auth/ConfigBoxWithToggleField";
import FromContainer from "./FromContainer";
import SMTPContainer from "./SMTPContainer";
import From from "./From";
import SMTP from "./SMTP";
interface Props {
form: FormApi;
submitting: boolean;
email: EmailConfigContainer_email;
}
export type FormProps = DeepNullable<Pick<GQLSettings, "email">>;
export type OnInitValuesFct = (values: DeepPartial<FormProps>) => void;
class EmailConfigContainer extends React.Component<Props> {
public static routeConfig: RouteProps;
private initialValues: DeepPartial<FormProps> = {};
public componentDidMount() {
this.props.form.initialize(this.initialValues);
}
private handleOnInitValues: OnInitValuesFct = values => {
if (
values.email &&
values.email.smtp &&
values.email.smtp.authentication === null
) {
const EmailConfigContainer: React.FunctionComponent<Props> = ({
email,
submitting,
}) => {
const form = useForm();
useMemo(() => {
let values: any = { email };
if (email && email.smtp && email.smtp.authentication === null) {
values = {
email: {
...values.email,
smtp: { ...values.email.smtp, authentication: true },
...email,
smtp: { ...email.smtp, authentication: true },
},
};
}
if (
values.email &&
values.email.smtp &&
values.email.smtp.secure === null
) {
if (email && email.smtp && email.smtp.secure === null) {
values = {
email: {
...values.email,
smtp: { ...values.email.smtp, secure: true },
...email,
smtp: { ...email.smtp, secure: true },
},
};
}
this.initialValues = pureMerge<DeepPartial<FormProps>>(
this.initialValues,
values
);
};
public render() {
const { email, submitting } = this.props;
return (
<ConfigBoxWithToggleField
disabled={submitting}
title={
<Localized id="configure-email">
<Header container="h2">Email settings</Header>
</Localized>
}
name="email.enabled"
>
{disabledInside => (
<>
<FromContainer
email={email}
disabled={disabledInside}
onInitValues={this.handleOnInitValues}
/>
<SMTPContainer
email={email}
disabled={disabledInside}
onInitValues={this.handleOnInitValues}
/>
</>
)}
</ConfigBoxWithToggleField>
);
}
}
form.initialize(purgeMetadata(values));
}, []);
return (
<ConfigBoxWithToggleField
disabled={submitting}
title={
<Localized id="configure-email">
<Header container="h2">Email settings</Header>
</Localized>
}
name="email.enabled"
>
{disabledInside => (
<>
<From disabled={disabledInside} />
<SMTP disabled={disabledInside} />
</>
)}
</ConfigBoxWithToggleField>
);
};
const enhanced = withFragmentContainer<Props>({
email: graphql`
fragment EmailConfigContainer_email on EmailConfiguration {
enabled
...FromContainer_email
...SMTPContainer_email
...From_formValues @relay(mask: false)
...SMTP_formValues @relay(mask: false)
}
`,
})(EmailConfigContainer);
@@ -1,4 +1,3 @@
import { FormApi } from "final-form";
import React from "react";
import { graphql } from "react-relay";
@@ -11,7 +10,6 @@ import EmailConfigContainer from "./EmailConfigContainer";
interface Props {
data: EmailConfigRouteQueryResponse | null;
form: FormApi;
submitting: boolean;
}
@@ -27,7 +25,6 @@ class EmailConfigRoute extends React.Component<Props> {
return (
<EmailConfigContainer
email={this.props.data.settings.email}
form={this.props.form}
submitting={this.props.submitting}
/>
);
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { parseEmptyAsNull } from "coral-framework/lib/form";
import { validateEmail } from "coral-framework/lib/validation";
@@ -13,6 +14,15 @@ import {
import TextFieldWithValidation from "../../TextFieldWithValidation";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment From_formValues on EmailConfiguration {
enabled
fromName
fromEmail
}
`;
interface Props {
disabled: boolean;
}
@@ -31,10 +41,10 @@ const From: FunctionComponent<Props> = ({ disabled }) => (
<Field name="email.fromName" parse={parseEmptyAsNull}>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
fullWidth
disabled={disabled}
meta={meta}
{...input}
/>
)}
</Field>
@@ -57,11 +67,11 @@ const From: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
type="email"
fullWidth
meta={meta}
disabled={disabled}
{...input}
/>
)}
</Field>
@@ -1,39 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { FromContainer_email } from "coral-admin/__generated__/FromContainer_email.graphql";
import { OnInitValuesFct } from "./EmailConfigContainer";
import From from "./From";
interface Props {
disabled: boolean;
onInitValues: OnInitValuesFct;
email: FromContainer_email;
}
class FromContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ email: props.email });
}
public render() {
const { disabled } = this.props;
return <From disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
email: graphql`
fragment FromContainer_email on EmailConfiguration {
enabled
fromName
fromEmail
}
`,
})(FromContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import {
colorFromMeta,
@@ -25,12 +26,26 @@ import Subheader from "../../Subheader";
import TextFieldWithValidation from "../../TextFieldWithValidation";
import { FormProps } from "./EmailConfigContainer";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment SMTP_formValues on EmailConfiguration {
enabled
smtp {
host
port
secure
authentication
username
password
}
}
`;
interface Props {
disabled: boolean;
}
const isEnabled: Condition<any, FormProps> = (value, values) =>
Boolean(values.email.enabled);
Boolean(values.email && values.email.enabled);
const isAuthenticating: Condition<any, FormProps> = (value, values) =>
Boolean(values.email.enabled && values.email.smtp.authentication);
@@ -52,11 +67,11 @@ const SMTP: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={input.name}
fullWidth
disabled={disabled}
meta={meta}
{...input}
/>
)}
</Field>
@@ -76,12 +91,12 @@ const SMTP: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={input.name}
type="number"
fullWidth
disabled={disabled}
meta={meta}
{...input}
/>
)}
</Field>
@@ -123,11 +138,11 @@ const SMTP: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={input.name}
disabled={disabled || !enabled}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
@@ -144,11 +159,11 @@ const SMTP: FunctionComponent<Props> = ({ disabled }) => (
{({ input, meta }) => (
<>
<PasswordField
{...input}
id={input.name}
disabled={disabled || !enabled}
fullWidth
color={colorFromMeta(meta)}
{...input}
/>
<ValidationMessage fullWidth meta={meta} />
</>
@@ -1,45 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { SMTPContainer_email } from "coral-admin/__generated__/SMTPContainer_email.graphql";
import { OnInitValuesFct } from "./EmailConfigContainer";
import SMTP from "./SMTP";
interface Props {
email: SMTPContainer_email;
disabled: boolean;
onInitValues: OnInitValuesFct;
}
class SMTPContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ email: props.email });
}
public render() {
const { disabled } = this.props;
return <SMTP disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
email: graphql`
fragment SMTPContainer_email on EmailConfiguration {
enabled
smtp {
host
port
secure
authentication
username
password
}
}
`,
})(SMTPContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, Suspense } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { MarkdownEditor } from "coral-framework/components/loadables";
import { parseEmptyAsNull } from "coral-framework/lib/form";
@@ -10,6 +11,15 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import ValidationMessage from "../../ValidationMessage";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment ClosedStreamMessageConfig_formValues on Settings {
closeCommenting {
message
}
}
`;
interface Props {
disabled: boolean;
}
@@ -37,8 +47,8 @@ const ClosedStreamMessageConfig: FunctionComponent<Props> = ({ disabled }) => (
<>
<Suspense fallback={<Spinner />}>
<MarkdownEditor
id="configure-general-closedStreamMessage-content"
{...input}
id="configure-general-closedStreamMessage-content"
/>
</Suspense>
<ValidationMessage meta={meta} fullWidth />
@@ -1,38 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { ClosedStreamMessageConfigContainer_settings as SettingsData } from "coral-admin/__generated__/ClosedStreamMessageConfigContainer_settings.graphql";
import ClosedStreamMessageConfig from "./ClosedStreamMessageConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class ClosedStreamMessageConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <ClosedStreamMessageConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment ClosedStreamMessageConfigContainer_settings on Settings {
closeCommenting {
message
}
}
`,
})(ClosedStreamMessageConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { colorFromMeta } from "coral-framework/lib/form";
import {
@@ -22,6 +23,16 @@ import Header from "../../Header";
import OnOffField from "../../OnOffField";
import ValidationMessage from "../../ValidationMessage";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment ClosingCommentStreamsConfig_formValues on Settings {
closeCommenting {
auto
timeout
}
}
`;
interface Props {
disabled: boolean;
}
@@ -67,6 +78,7 @@ const ClosingCommentStreamsConfig: FunctionComponent<Props> = ({
{({ input, meta }) => (
<>
<DurationField
{...input}
units={[
DURATION_UNIT.HOURS,
DURATION_UNIT.DAYS,
@@ -74,7 +86,6 @@ const ClosingCommentStreamsConfig: FunctionComponent<Props> = ({
]}
disabled={disabled}
color={colorFromMeta(meta)}
{...input}
/>
<ValidationMessage meta={meta} fullWidth />
</>
@@ -1,39 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { ClosingCommentStreamsConfigContainer_settings as SettingsData } from "coral-admin/__generated__/ClosingCommentStreamsConfigContainer_settings.graphql";
import ClosingCommentStreamsConfig from "./ClosingCommentStreamsConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class ClosingCommentStreamsConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <ClosingCommentStreamsConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment ClosingCommentStreamsConfigContainer_settings on Settings {
closeCommenting {
auto
timeout
}
}
`,
})(ClosingCommentStreamsConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { colorFromMeta } from "coral-framework/lib/form";
import {
@@ -21,6 +22,12 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import ValidationMessage from "../../ValidationMessage";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment CommentEditingConfig_formValues on Settings {
editCommentWindowLength
}
`;
interface Props {
disabled: boolean;
}
@@ -58,6 +65,7 @@ const CommentEditingConfig: FunctionComponent<Props> = ({ disabled }) => (
{({ input, meta }) => (
<>
<DurationField
{...input}
units={[
DURATION_UNIT.SECONDS,
DURATION_UNIT.MINUTES,
@@ -65,7 +73,6 @@ const CommentEditingConfig: FunctionComponent<Props> = ({ disabled }) => (
]}
color={colorFromMeta(meta)}
disabled={disabled}
{...input}
/>
<ValidationMessage meta={meta} fullWidth />
</>
@@ -1,36 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { CommentEditingConfigContainer_settings as SettingsData } from "coral-admin/__generated__/CommentEditingConfigContainer_settings.graphql";
import CommentEditingConfig from "./CommentEditingConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class CommentEditingConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <CommentEditingConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment CommentEditingConfigContainer_settings on Settings {
editCommentWindowLength
}
`,
})(CommentEditingConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { formatEmpty, parseEmptyAsNull } from "coral-framework/lib/form";
import {
@@ -23,8 +24,19 @@ import TextFieldWithValidation from "../../TextFieldWithValidation";
import styles from "./CommentLengthConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment CommentLengthConfig_formValues on Settings {
charCount {
enabled
min
max
}
}
`;
const validateMaxLongerThanMin = createValidator(
(v, values) =>
(v: any, values: any) =>
v === null ||
values.charCount.min === null ||
parseInt(v, 10) > parseInt(values.charCount.min, 10),
@@ -82,11 +94,11 @@ const CommentLengthConfig: FunctionComponent<Props> = ({ disabled }) => (
attrs={{ placeholder: true }}
>
<TextFieldWithValidation
{...input}
id="configure-general-commentLength-min"
classes={{
input: styles.commentLengthTextInput,
}}
{...input}
disabled={disabled}
autoComplete="off"
meta={meta}
@@ -126,6 +138,7 @@ const CommentLengthConfig: FunctionComponent<Props> = ({ disabled }) => (
attrs={{ placeholder: true }}
>
<TextFieldWithValidation
{...input}
id="configure-general-commentLength-max"
classes={{
input: styles.commentLengthTextInput,
@@ -143,7 +156,6 @@ const CommentLengthConfig: FunctionComponent<Props> = ({ disabled }) => (
placeholder={"No limit"}
textAlignCenter
meta={meta}
{...input}
/>
</Localized>
)}
@@ -1,40 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { CommentLengthConfigContainer_settings as SettingsData } from "coral-admin/__generated__/CommentLengthConfigContainer_settings.graphql";
import CommentLengthConfig from "./CommentLengthConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class CommentLengthConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <CommentLengthConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment CommentLengthConfigContainer_settings on Settings {
charCount {
enabled
min
max
}
}
`,
})(CommentLengthConfigContainer);
export default enhanced;
@@ -1,84 +0,0 @@
import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components/v2";
import ClosedStreamMessageConfigContainer from "./ClosedStreamMessageConfigContainer";
import ClosingCommentStreamsConfigContainer from "./ClosingCommentStreamsConfigContainer";
import CommentEditingConfigContainer from "./CommentEditingConfigContainer";
import CommentLengthConfigContainer from "./CommentLengthConfigContainer";
import GuidelinesConfigContainer from "./GuidelinesConfigContainer";
import LocaleConfigContainer from "./LocaleConfigContainer";
import ReactionConfigContainer from "./ReactionConfigContainer";
import SitewideCommentingConfigContainer from "./SitewideCommentingConfigContainer";
import StaffConfigContainer from "./StaffConfigContainer";
interface Props {
disabled: boolean;
settings: PropTypesOf<typeof GuidelinesConfigContainer>["settings"] &
PropTypesOf<typeof CommentLengthConfigContainer>["settings"] &
PropTypesOf<typeof CommentEditingConfigContainer>["settings"] &
PropTypesOf<typeof ClosedStreamMessageConfigContainer>["settings"] &
PropTypesOf<typeof ReactionConfigContainer>["settings"] &
PropTypesOf<typeof StaffConfigContainer>["settings"] &
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"] &
PropTypesOf<typeof SitewideCommentingConfigContainer>["settings"] &
PropTypesOf<typeof LocaleConfigContainer>["settings"];
onInitValues: (values: any) => void;
}
const General: FunctionComponent<Props> = ({
disabled,
settings,
onInitValues,
}) => (
<HorizontalGutter size="double" data-testid="configure-generalContainer">
<LocaleConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<SitewideCommentingConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<GuidelinesConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<CommentLengthConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<CommentEditingConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<ClosingCommentStreamsConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<ClosedStreamMessageConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<ReactionConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<StaffConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
</HorizontalGutter>
);
export default General;
@@ -1,60 +1,65 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import React from "react";
import React, { useMemo } from "react";
import { useForm } from "react-final-form";
import { graphql } from "react-relay";
import { pureMerge } from "coral-common/utils";
import { withFragmentContainer } from "coral-framework/lib/relay";
import {
purgeMetadata,
withFragmentContainer,
} from "coral-framework/lib/relay";
import { HorizontalGutter } from "coral-ui/components";
import { GeneralConfigContainer_settings as SettingsData } from "coral-admin/__generated__/GeneralConfigContainer_settings.graphql";
import GeneralConfig from "./GeneralConfig";
import ClosedStreamMessageConfig from "./ClosedStreamMessageConfig";
import ClosingCommentStreamsConfig from "./ClosingCommentStreamsConfig";
import CommentEditingConfig from "./CommentEditingConfig";
import CommentLengthConfig from "./CommentLengthConfig";
import GuidelinesConfig from "./GuidelinesConfig";
import LocaleConfig from "./LocaleConfig";
import ReactionConfigContainer from "./ReactionConfigContainer";
import SitewideCommentingConfig from "./SitewideCommentingConfig";
import StaffConfig from "./StaffConfig";
interface Props {
form: FormApi;
submitting: boolean;
settings: SettingsData;
}
class GeneralConfigContainer extends React.Component<Props> {
public static routeConfig: RouteProps;
private initialValues = {};
constructor(props: Props) {
super(props);
}
public componentDidMount() {
this.props.form.initialize(this.initialValues);
}
private handleOnInitValues = (values: any) => {
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
return (
<GeneralConfig
disabled={this.props.submitting}
settings={this.props.settings}
onInitValues={this.handleOnInitValues}
/>
);
}
}
const GeneralConfigContainer: React.FunctionComponent<Props> = ({
settings,
submitting,
}) => {
const form = useForm();
useMemo(() => form.initialize(purgeMetadata(settings)), []);
return (
<HorizontalGutter size="double" data-testid="configure-generalContainer">
<LocaleConfig disabled={submitting} />
<SitewideCommentingConfig disabled={submitting} />
<GuidelinesConfig disabled={submitting} />
<CommentLengthConfig disabled={submitting} />
<CommentEditingConfig disabled={submitting} />
<ClosingCommentStreamsConfig disabled={submitting} />
<ClosedStreamMessageConfig disabled={submitting} />
<ReactionConfigContainer disabled={submitting} settings={settings} />
<StaffConfig disabled={submitting} />
</HorizontalGutter>
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment GeneralConfigContainer_settings on Settings {
...LocaleConfigContainer_settings
...GuidelinesConfigContainer_settings
...CommentLengthConfigContainer_settings
...CommentEditingConfigContainer_settings
...ClosedStreamMessageConfigContainer_settings
...ClosingCommentStreamsConfigContainer_settings
...SitewideCommentingConfigContainer_settings
...LocaleConfig_formValues @relay(mask: false)
...GuidelinesConfig_formValues @relay(mask: false)
...CommentLengthConfig_formValues @relay(mask: false)
...CommentEditingConfig_formValues @relay(mask: false)
...ClosedStreamMessageConfig_formValues @relay(mask: false)
...ClosingCommentStreamsConfig_formValues @relay(mask: false)
...SitewideCommentingConfig_formValues @relay(mask: false)
...ReactionConfig_formValues @relay(mask: false)
...StaffConfig_formValues @relay(mask: false)
...ReactionConfigContainer_settings
...StaffConfigContainer_settings
}
`,
})(GeneralConfigContainer);
@@ -1,4 +1,3 @@
import { FormApi } from "final-form";
import React from "react";
import { graphql } from "react-relay";
@@ -12,7 +11,6 @@ import GeneralConfigContainer from "./GeneralConfigContainer";
interface Props {
data: GeneralConfigRouteQueryResponse | null;
form: FormApi;
submitting: boolean;
}
@@ -28,7 +26,6 @@ class GeneralConfigRoute extends React.Component<Props> {
return (
<GeneralConfigContainer
settings={this.props.data.settings}
form={this.props.form}
submitting={this.props.submitting}
/>
);
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, Suspense } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { MarkdownEditor } from "coral-framework/components/loadables";
import { parseEmptyAsNull } from "coral-framework/lib/form";
@@ -19,6 +20,16 @@ import Header from "../../Header";
import OnOffField from "../../OnOffField";
import ValidationMessage from "../../ValidationMessage";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment GuidelinesConfig_formValues on Settings {
communityGuidelines {
enabled
content
}
}
`;
interface Props {
disabled: boolean;
}
@@ -65,8 +76,8 @@ const GuidelinesConfig: FunctionComponent<Props> = ({ disabled }) => (
<>
<Suspense fallback={<Spinner />}>
<MarkdownEditor
id="configure-general-guidelines-content"
{...input}
id="configure-general-guidelines-content"
/>
</Suspense>
<ValidationMessage meta={meta} />
@@ -1,39 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { GuidelinesConfigContainer_settings as SettingsData } from "coral-admin/__generated__/GuidelinesConfigContainer_settings.graphql";
import GuidelinesConfig from "./GuidelinesConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class GuidelinesConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <GuidelinesConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment GuidelinesConfigContainer_settings on Settings {
communityGuidelines {
enabled
content
}
}
`,
})(GuidelinesConfigContainer);
export default enhanced;
@@ -1,27 +1,28 @@
import { Localized } from "fluent-react/compat";
import React, { useMemo } from "react";
import React from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { required } from "coral-framework/lib/validation";
import { FormFieldDescription, HorizontalGutter } from "coral-ui/components/v2";
import { LocaleConfigContainer_settings } from "coral-admin/__generated__/LocaleConfigContainer_settings.graphql";
import ConfigBox from "../../ConfigBox";
import LocaleField from "../../Fields/LocaleField";
import Header from "../../Header";
import ValidationMessage from "../../ValidationMessage";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment LocaleConfig_formValues on Settings {
locale
}
`;
interface Props {
settings: LocaleConfigContainer_settings;
onInitValues: (values: LocaleConfigContainer_settings) => void;
disabled: boolean;
}
const LocaleConfigContainer: React.FunctionComponent<Props> = props => {
useMemo(() => props.onInitValues(props.settings), [props.onInitValues]);
const LocaleConfig: React.FunctionComponent<Props> = props => {
return (
<ConfigBox
title={
@@ -40,9 +41,9 @@ const LocaleConfigContainer: React.FunctionComponent<Props> = props => {
{({ input, meta }) => (
<>
<LocaleField
{...input}
id={`configure-locale-${input.name}`}
disabled={props.disabled}
{...input}
/>
<ValidationMessage meta={meta} fullWidth />
</>
@@ -53,12 +54,4 @@ const LocaleConfigContainer: React.FunctionComponent<Props> = props => {
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment LocaleConfigContainer_settings on Settings {
locale
}
`,
})(LocaleConfigContainer);
export default enhanced;
export default LocaleConfig;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { required } from "coral-framework/lib/validation";
import ReactionButton from "coral-stream/tabs/Comments/Comment/ReactionButton/ReactionButton";
@@ -15,20 +16,36 @@ import {
SelectField,
} from "coral-ui/components/v2";
import { ReactionConfigContainer_settings as SettingsData } from "coral-admin/__generated__/ReactionConfigContainer_settings.graphql";
import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import TextFieldWithValidation from "../../TextFieldWithValidation";
import styles from "./ReactionConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment ReactionConfig_formValues on Settings {
reaction {
label
labelActive
sortLabel
icon
iconActive
}
}
`;
interface Props {
icon: string;
iconActive: string | null;
disabled: boolean;
settings: SettingsData;
}
const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
const ReactionsConfig: FunctionComponent<Props> = ({
disabled,
icon,
iconActive,
}) => (
<ConfigBox
title={
<Localized id="configure-general-reactions-title">
@@ -54,6 +71,7 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
</Localized>
<Localized id="configure-general-reactions-input">
<TextFieldWithValidation
{...input}
className={styles.textInput}
id={input.name}
type="text"
@@ -61,7 +79,6 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
placeholder="E.g. Respect"
disabled={disabled}
meta={meta}
{...input}
/>
</Localized>
</FormField>
@@ -74,9 +91,9 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
className={styles.reactionButton}
reacted={false}
label={input.value}
labelActive={settings.reaction.labelActive}
icon={settings.reaction.icon}
iconActive={settings.reaction.iconActive}
labelActive={input.value}
icon={icon}
iconActive={iconActive}
totalReactions={0}
onClick={() => null}
/>
@@ -93,6 +110,7 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
</Localized>
<Localized id="configure-general-reactions-active-input">
<TextFieldWithValidation
{...input}
className={styles.textInput}
id={input.name}
type="text"
@@ -100,7 +118,6 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
fullWidth
disabled={disabled}
meta={meta}
{...input}
/>
</Localized>
</FormField>
@@ -112,10 +129,10 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
className={styles.reactionButton}
readOnly
reacted
label={settings.reaction.label}
label={input.value}
labelActive={input.value}
icon={settings.reaction.icon}
iconActive={settings.reaction.iconActive}
icon={icon}
iconActive={iconActive}
totalReactions={0}
onClick={() => null}
/>
@@ -132,6 +149,7 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
</Localized>
<Localized id="configure-general-reactions-sort-input">
<TextFieldWithValidation
{...input}
id={input.name}
className={styles.textInput}
type="text"
@@ -139,7 +157,6 @@ const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
fullWidth
disabled={disabled}
meta={meta}
{...input}
/>
</Localized>
</FormField>
@@ -9,29 +9,26 @@ import ReactionConfig from "./ReactionConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class ReactionConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled, settings } = this.props;
return <ReactionConfig settings={settings} disabled={disabled} />;
}
}
const ReactionConfigContainer: React.FunctionComponent<Props> = ({
disabled,
settings,
}) => {
return (
<ReactionConfig
iconActive={settings.reaction.iconActive}
icon={settings.reaction.icon}
disabled={disabled}
/>
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment ReactionConfigContainer_settings on Settings {
reaction {
label
labelActive
sortLabel
icon
iconActive
}
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, Suspense } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { MarkdownEditor } from "coral-framework/components/loadables";
import { parseEmptyAsNull } from "coral-framework/lib/form";
@@ -19,6 +20,16 @@ import Header from "../../Header";
import OnOffField from "../../OnOffField";
import ValidationMessage from "../../ValidationMessage";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment SitewideCommentingConfig_formValues on Settings {
disableCommenting {
enabled
message
}
}
`;
interface Props {
disabled: boolean;
}
@@ -82,8 +93,8 @@ const SitewideCommentingConfig: FunctionComponent<Props> = ({ disabled }) => (
<>
<Suspense fallback={<Spinner />}>
<MarkdownEditor
id="configure-general-sitewideCommenting-message"
{...input}
id="configure-general-sitewideCommenting-message"
/>
</Suspense>
<ValidationMessage meta={meta} fullWidth />
@@ -1,39 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { SitewideCommentingConfigContainer_settings as SettingsData } from "coral-admin/__generated__/SitewideCommentingConfigContainer_settings.graphql";
import SitewideCommentingConfig from "./SitewideCommentingConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class SitewideCommentingConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <SitewideCommentingConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment SitewideCommentingConfigContainer_settings on Settings {
disableCommenting {
enabled
message
}
}
`,
})(SitewideCommentingConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { required } from "coral-framework/lib/validation";
import {
@@ -13,20 +14,26 @@ import {
Tag,
} from "coral-ui/components/v2";
import { StaffConfigContainer_settings as SettingsData } from "coral-admin/__generated__/StaffConfigContainer_settings.graphql";
import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import TextFieldWithValidation from "../../TextFieldWithValidation";
import styles from "./StaffConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment StaffConfig_formValues on Settings {
staff {
label
}
}
`;
interface Props {
disabled: boolean;
settings: SettingsData;
}
const StaffConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
const StaffConfig: FunctionComponent<Props> = ({ disabled }) => (
<ConfigBox
title={
<Localized id="configure-general-staff-title">
@@ -50,6 +57,7 @@ const StaffConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
</Localized>
<Localized id="configure-general-staff-input">
<TextFieldWithValidation
{...input}
className={styles.textInput}
id={input.name}
type="text"
@@ -57,7 +65,6 @@ const StaffConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
placeholder="E.g. Staff"
disabled={disabled}
meta={meta}
{...input}
/>
</Localized>
</FormField>
@@ -1,38 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { StaffConfigContainer_settings as SettingsData } from "coral-admin/__generated__/StaffConfigContainer_settings.graphql";
import StaffConfig from "./StaffConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class StaffConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled, settings } = this.props;
return <StaffConfig settings={settings} disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment StaffConfigContainer_settings on Settings {
staff {
label
}
}
`,
})(StaffConfigContainer);
export default enhanced;
@@ -29,6 +29,7 @@ const APIKeyField: FunctionComponent<Props> = ({
</Label>
</Localized>
<PasswordField
{...input}
id={`configure-moderation-${input.name}`}
disabled={disabled}
// TODO: (wyattjoh) figure out how to add translations to these props
@@ -36,7 +37,6 @@ const APIKeyField: FunctionComponent<Props> = ({
showPasswordTitle="Hide API Key"
color={colorFromMeta(meta)}
fullWidth
{...input}
/>
<ValidationMessage meta={meta} />
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { parseEmptyAsNull } from "coral-framework/lib/form";
import { ExternalLink } from "coral-framework/lib/i18n/components";
@@ -25,6 +26,19 @@ import Subheader from "../../Subheader";
import TextFieldWithValidation from "../../TextFieldWithValidation";
import APIKeyField from "./APIKeyField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment AkismetConfig_formValues on Settings {
integrations {
akismet {
enabled
key
site
}
}
}
`;
interface Props {
disabled: boolean;
}
@@ -95,6 +109,7 @@ const AkismetConfig: FunctionComponent<Props> = ({ disabled }) => {
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id="configure-moderation-akismet-site"
disabled={disabled}
autoComplete="off"
@@ -103,7 +118,6 @@ const AkismetConfig: FunctionComponent<Props> = ({ disabled }) => {
spellCheck={false}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
@@ -1,42 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { AkismetConfigContainer_settings as SettingsData } from "coral-admin/__generated__/AkismetConfigContainer_settings.graphql";
import AkismetConfig from "./AkismetConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class AkismetConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <AkismetConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment AkismetConfigContainer_settings on Settings {
integrations {
akismet {
enabled
key
site
}
}
}
`,
})(AkismetConfigContainer);
export default enhanced;
@@ -1,21 +0,0 @@
import { noop } from "lodash";
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import { removeFragmentRefs } from "coral-framework/testHelpers";
import { PropTypesOf } from "coral-framework/types";
import ModerationConfig from "./ModerationConfig";
const ModerationConfigN = removeFragmentRefs(ModerationConfig);
it("renders correctly", () => {
const props: PropTypesOf<typeof ModerationConfigN> = {
disabled: false,
settings: {},
onInitValues: noop,
};
const renderer = createRenderer();
renderer.render(<ModerationConfigN {...props} />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
@@ -1,49 +0,0 @@
import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components/v2";
import AkismetConfigContainer from "./AkismetConfigContainer";
import PerspectiveConfigContainer from "./PerspectiveConfigContainer";
import PreModerationConfigContainer from "./PreModerationConfigContainer";
import RecentCommentHistoryConfigContainer from "./RecentCommentHistoryConfigContainer";
interface Props {
disabled: boolean;
settings: PropTypesOf<typeof AkismetConfigContainer>["settings"] &
PropTypesOf<typeof PerspectiveConfigContainer>["settings"] &
PropTypesOf<typeof PreModerationConfigContainer>["settings"] &
PropTypesOf<typeof RecentCommentHistoryConfigContainer>["settings"];
onInitValues: (values: any) => void;
}
const ModerationConfig: FunctionComponent<Props> = ({
disabled,
settings,
onInitValues,
}) => (
<HorizontalGutter size="double" data-testid="configure-moderationContainer">
<PreModerationConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<RecentCommentHistoryConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<PerspectiveConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<AkismetConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
</HorizontalGutter>
);
export default ModerationConfig;
@@ -1,55 +1,48 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import React from "react";
import React, { useMemo } from "react";
import { useForm } from "react-final-form";
import { graphql } from "react-relay";
import { pureMerge } from "coral-common/utils";
import { withFragmentContainer } from "coral-framework/lib/relay";
import {
purgeMetadata,
withFragmentContainer,
} from "coral-framework/lib/relay";
import { HorizontalGutter } from "coral-ui/components";
import { ModerationConfigContainer_settings as SettingsData } from "coral-admin/__generated__/ModerationConfigContainer_settings.graphql";
import ModerationConfig from "./ModerationConfig";
import AkismetConfig from "./AkismetConfig";
import PerspectiveConfig from "./PerspectiveConfig";
import PreModerationConfig from "./PreModerationConfig";
import RecentCommentHistoryConfig from "./RecentCommentHistoryConfig";
interface Props {
form: FormApi;
submitting: boolean;
settings: SettingsData;
}
class ModerationConfigContainer extends React.Component<Props> {
public static routeConfig: RouteProps;
private initialValues = {};
constructor(props: Props) {
super(props);
}
public componentDidMount() {
this.props.form.initialize(this.initialValues);
}
private handleOnInitValues = (values: any) => {
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
return (
<ModerationConfig
disabled={this.props.submitting}
settings={this.props.settings}
onInitValues={this.handleOnInitValues}
/>
);
}
}
export const ModerationConfigContainer: React.FunctionComponent<Props> = ({
settings,
submitting,
}) => {
const form = useForm();
useMemo(() => form.initialize(purgeMetadata(settings)), []);
return (
<HorizontalGutter size="double" data-testid="configure-moderationContainer">
<PreModerationConfig disabled={submitting} />
<RecentCommentHistoryConfig disabled={submitting} />
<PerspectiveConfig disabled={submitting} />
<AkismetConfig disabled={submitting} />
</HorizontalGutter>
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment ModerationConfigContainer_settings on Settings {
...AkismetConfigContainer_settings
...PerspectiveConfigContainer_settings
...PreModerationConfigContainer_settings
...RecentCommentHistoryConfigContainer_settings
...AkismetConfig_formValues @relay(mask: false)
...PerspectiveConfig_formValues @relay(mask: false)
...PreModerationConfig_formValues @relay(mask: false)
...RecentCommentHistoryConfig_formValues @relay(mask: false)
}
`,
})(ModerationConfigContainer);
@@ -1,4 +1,3 @@
import { FormApi } from "final-form";
import React from "react";
import { graphql } from "react-relay";
@@ -11,7 +10,6 @@ import ModerationConfigContainer from "./ModerationConfigContainer";
interface Props {
data: ModerationConfigRouteQueryResponse | null;
form: FormApi;
submitting: boolean;
}
@@ -27,7 +25,6 @@ class ModerationConfigRoute extends React.Component<Props> {
return (
<ModerationConfigContainer
settings={this.props.data.settings}
form={this.props.form}
submitting={this.props.submitting}
/>
);
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import {
TOXICITY_ENDPOINT_DEFAULT,
@@ -34,13 +35,28 @@ import {
import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import OnOffField from "../../OnOffField";
import PermissionField from "../../PermissionField";
import Subheader from "../../Subheader";
import TextFieldWithValidation from "../../TextFieldWithValidation";
import APIKeyField from "./APIKeyField";
import styles from "./PerspectiveConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment PerspectiveConfig_formValues on Settings {
integrations {
perspective {
enabled
endpoint
key
model
threshold
doNotStore
}
}
}
`;
interface Props {
disabled: boolean;
}
@@ -108,6 +124,7 @@ const PerspectiveConfig: FunctionComponent<Props> = ({ disabled }) => {
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id="configure-moderation-perspective-threshold"
classes={{
input: styles.thresholdTextField,
@@ -121,7 +138,6 @@ const PerspectiveConfig: FunctionComponent<Props> = ({ disabled }) => {
placeholder={TOXICITY_THRESHOLD_DEFAULT.toString()}
textAlignCenter
meta={meta}
{...input}
/>
)}
</Field>
@@ -154,6 +170,7 @@ const PerspectiveConfig: FunctionComponent<Props> = ({ disabled }) => {
<Field name="integrations.perspective.model" parse={parseEmptyAsNull}>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id="configure-moderation-perspective-model"
disabled={disabled}
autoComplete="off"
@@ -163,7 +180,6 @@ const PerspectiveConfig: FunctionComponent<Props> = ({ disabled }) => {
spellCheck={false}
meta={meta}
fullWidth
{...input}
/>
)}
</Field>
@@ -181,9 +197,19 @@ const PerspectiveConfig: FunctionComponent<Props> = ({ disabled }) => {
</HelperText>
</Localized>
</FormFieldHeader>
<PermissionField
<OnOffField
name="integrations.perspective.doNotStore"
disabled={disabled}
onLabel={
<Localized id="configure-radioButton-allow">
<span>Allow</span>
</Localized>
}
offLabel={
<Localized id="configure-radioButton-dontAllow">
<span>Don't Allow</span>
</Localized>
}
invert
/>
</FormField>
@@ -229,6 +255,7 @@ const PerspectiveConfig: FunctionComponent<Props> = ({ disabled }) => {
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id="configure-moderation-perspective-customEndpoint"
disabled={disabled}
autoComplete="off"
@@ -238,7 +265,6 @@ const PerspectiveConfig: FunctionComponent<Props> = ({ disabled }) => {
spellCheck={false}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
@@ -1,45 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { PerspectiveConfigContainer_settings as SettingsData } from "coral-admin/__generated__/PerspectiveConfigContainer_settings.graphql";
import PerspectiveConfig from "./PerspectiveConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class PerspectiveConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <PerspectiveConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment PerspectiveConfigContainer_settings on Settings {
integrations {
perspective {
enabled
endpoint
key
model
threshold
doNotStore
}
}
}
`,
})(PerspectiveConfigContainer);
export default enhanced;
@@ -1,7 +1,8 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import { parseStringBool } from "coral-framework/lib/form";
import { formatBool, parseStringBool } from "coral-framework/lib/form";
import {
FieldSet,
FormField,
@@ -13,6 +14,14 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import OnOffField from "../../OnOffField";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment PreModerationConfig_formValues on Settings {
moderation
premodLinksEnable
}
`;
interface Props {
disabled: boolean;
}
@@ -22,7 +31,7 @@ const parse = (v: string) => {
};
const format = (v: "PRE" | "POST") => {
return v === "PRE";
return formatBool(v === "PRE");
};
const PreModerationConfig: FunctionComponent<Props> = ({ disabled }) => {
@@ -1,37 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { PreModerationConfigContainer_settings as SettingsData } from "coral-admin/__generated__/PreModerationConfigContainer_settings.graphql";
import PreModerationConfig from "./PreModerationConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class PreModerationConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <PreModerationConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment PreModerationConfigContainer_settings on Settings {
moderation
premodLinksEnable
}
`,
})(PreModerationConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { formatPercentage, parsePercentage } from "coral-framework/lib/form";
import { hasError } from "coral-framework/lib/form/helpers";
@@ -29,6 +30,17 @@ import ValidationMessage from "../../ValidationMessage";
import styles from "./RecentCommentHistoryConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment RecentCommentHistoryConfig_formValues on Settings {
recentCommentHistory {
enabled
timeFrame
triggerRejectionRate
}
}
`;
interface Props {
disabled: boolean;
}
@@ -117,6 +129,7 @@ const RecentCommentHistoryConfig: FunctionComponent<Props> = ({ disabled }) => {
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
classes={{
input: styles.thresholdTextField,
}}
@@ -128,7 +141,6 @@ const RecentCommentHistoryConfig: FunctionComponent<Props> = ({ disabled }) => {
adornment={<TextFieldAdornment>%</TextFieldAdornment>}
meta={meta}
textAlignCenter
{...input}
/>
)}
</Field>
@@ -1,40 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { RecentCommentHistoryConfigContainer_settings as SettingsData } from "coral-admin/__generated__/RecentCommentHistoryConfigContainer_settings.graphql";
import RecentCommentHistoryConfig from "./RecentCommentHistoryConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class RecentCommentHistoryConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <RecentCommentHistoryConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment RecentCommentHistoryConfigContainer_settings on Settings {
recentCommentHistory {
enabled
timeFrame
triggerRejectionRate
}
}
`,
})(RecentCommentHistoryConfigContainer);
export default enhanced;
@@ -1,29 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<ForwardRef(forwardRef)
data-testid="configure-moderationContainer"
size="double"
>
<Relay(PreModerationConfigContainer)
disabled={false}
onInitValues={[Function]}
settings={Object {}}
/>
<Relay(RecentCommentHistoryConfigContainer)
disabled={false}
onInitValues={[Function]}
settings={Object {}}
/>
<Relay(PerspectiveConfigContainer)
disabled={false}
onInitValues={[Function]}
settings={Object {}}
/>
<Relay(AkismetConfigContainer)
disabled={false}
onInitValues={[Function]}
settings={Object {}}
/>
</ForwardRef(forwardRef)>
`;
@@ -1,42 +0,0 @@
import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components/v2";
import OrganizationContactEmailConfigContainer from "./OrganizationContactEmailConfigContainer";
import OrganizationNameConfigContainer from "./OrganizationNameConfigContainer";
import OrganizationURLConfigContainer from "./OrganizationURLConfigContainer";
interface Props {
disabled: boolean;
settings: PropTypesOf<typeof OrganizationNameConfigContainer>["settings"] &
PropTypesOf<typeof OrganizationContactEmailConfigContainer>["settings"] &
PropTypesOf<typeof OrganizationURLConfigContainer>["settings"];
onInitValues: (values: any) => void;
}
const OrganizationConfig: FunctionComponent<Props> = ({
disabled,
settings,
onInitValues,
}) => (
<HorizontalGutter size="double" data-testid="configure-organizationContainer">
<OrganizationNameConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<OrganizationContactEmailConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
<OrganizationURLConfigContainer
disabled={disabled}
settings={settings}
onInitValues={onInitValues}
/>
</HorizontalGutter>
);
export default OrganizationConfig;
@@ -1,56 +1,47 @@
import { FormApi } from "final-form";
import { RouteProps } from "found";
import React from "react";
import React, { useMemo } from "react";
import { useForm } from "react-final-form";
import { graphql } from "react-relay";
import { pureMerge } from "coral-common/utils";
import { withFragmentContainer } from "coral-framework/lib/relay";
import {
purgeMetadata,
withFragmentContainer,
} from "coral-framework/lib/relay";
import { HorizontalGutter } from "coral-ui/components";
import { OrganizationConfigContainer_settings as SettingsData } from "coral-admin/__generated__/OrganizationConfigContainer_settings.graphql";
import OrganizationConfig from "./OrganizationConfig";
import OrganizationContactEmailConfig from "./OrganizationContactEmailConfig";
import OrganizationNameConfig from "./OrganizationNameConfig";
import OrganizationURLConfig from "./OrganizationURLConfig";
interface Props {
form: FormApi;
submitting: boolean;
settings: SettingsData;
}
class OrganizationConfigContainer extends React.Component<Props> {
public static routeConfig: RouteProps;
private initialValues = {};
constructor(props: Props) {
super(props);
}
public componentDidMount() {
this.props.form.initialize(this.initialValues);
}
private handleOnInitValues = (values: any) => {
this.initialValues = pureMerge(this.initialValues, values);
};
public render() {
return (
<OrganizationConfig
disabled={this.props.submitting}
settings={this.props.settings}
onInitValues={this.handleOnInitValues}
/>
);
}
}
const OrganizationConfigContainer: React.FunctionComponent<Props> = ({
settings,
submitting,
}) => {
const form = useForm();
useMemo(() => form.initialize(purgeMetadata(settings)), []);
return (
<HorizontalGutter
size="double"
data-testid="configure-organizationContainer"
>
<OrganizationNameConfig disabled={submitting} />
<OrganizationContactEmailConfig disabled={submitting} />
<OrganizationURLConfig disabled={submitting} />
</HorizontalGutter>
);
};
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment OrganizationConfigContainer_settings on Settings {
...OrganizationNameConfigContainer_settings
...OrganizationContactEmailConfigContainer_settings
...OrganizationURLConfigContainer_settings
...OrganizationNameConfig_formValues @relay(mask: false)
...OrganizationContactEmailConfig_formValues @relay(mask: false)
...OrganizationURLConfig_formValues @relay(mask: false)
}
`,
})(OrganizationConfigContainer);
export default enhanced;
@@ -1,4 +1,3 @@
import { FormApi } from "final-form";
import React from "react";
import { graphql } from "react-relay";
@@ -11,7 +10,6 @@ import OrganizationConfigContainer from "./OrganizationConfigContainer";
interface Props {
data: OrganizationConfigRouteQueryResponse | null;
form: FormApi;
submitting: boolean;
}
@@ -27,7 +25,6 @@ class OrganizationConfigRoute extends React.Component<Props> {
return (
<OrganizationConfigContainer
settings={this.props.data.settings}
form={this.props.form}
submitting={this.props.submitting}
/>
);
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import {
composeValidators,
@@ -13,11 +14,22 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import TextFieldWithValidation from "../../TextFieldWithValidation";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment OrganizationContactEmailConfig_formValues on Settings {
organization {
contactEmail
}
}
`;
interface Props {
disabled: boolean;
}
const OrganizationNameConfig: FunctionComponent<Props> = ({ disabled }) => (
const OrganizationContactEmailConfig: FunctionComponent<Props> = ({
disabled,
}) => (
<ConfigBox
title={
<Localized id="configure-organization-email">
@@ -41,6 +53,7 @@ const OrganizationNameConfig: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={`configure-organization-${input.name}`}
disabled={disabled}
autoComplete="off"
@@ -49,11 +62,10 @@ const OrganizationNameConfig: FunctionComponent<Props> = ({ disabled }) => (
spellCheck={false}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
</ConfigBox>
);
export default OrganizationNameConfig;
export default OrganizationContactEmailConfig;
@@ -1,38 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { OrganizationContactEmailConfigContainer_settings as SettingsData } from "coral-admin/__generated__/OrganizationContactEmailConfigContainer_settings.graphql";
import OrganizationContactEmailConfig from "./OrganizationContactEmailConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class OrganizationContactEmailConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <OrganizationContactEmailConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment OrganizationContactEmailConfigContainer_settings on Settings {
organization {
contactEmail
}
}
`,
})(OrganizationContactEmailConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { required } from "coral-framework/lib/validation";
import { FormFieldDescription } from "coral-ui/components/v2";
@@ -9,6 +10,15 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import TextFieldWithValidation from "../../TextFieldWithValidation";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment OrganizationNameConfig_formValues on Settings {
organization {
name
}
}
`;
interface Props {
disabled: boolean;
}
@@ -32,6 +42,7 @@ const OrganizationNameConfig: FunctionComponent<Props> = ({ disabled }) => (
<Field name="organization.name" validate={required}>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={`configure-organization-${input.name}`}
disabled={disabled}
autoComplete="off"
@@ -40,7 +51,6 @@ const OrganizationNameConfig: FunctionComponent<Props> = ({ disabled }) => (
spellCheck={false}
meta={meta}
fullWidth
{...input}
/>
)}
</Field>
@@ -1,38 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { OrganizationNameConfigContainer_settings as SettingsData } from "coral-admin/__generated__/OrganizationNameConfigContainer_settings.graphql";
import OrganizationNameConfig from "./OrganizationNameConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class OrganizationNameConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <OrganizationNameConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment OrganizationNameConfigContainer_settings on Settings {
organization {
name
}
}
`,
})(OrganizationNameConfigContainer);
export default enhanced;
@@ -1,6 +1,7 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import {
composeValidators,
@@ -13,6 +14,15 @@ import ConfigBox from "../../ConfigBox";
import Header from "../../Header";
import TextFieldWithValidation from "../../TextFieldWithValidation";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment OrganizationURLConfig_formValues on Settings {
organization {
url
}
}
`;
interface Props {
disabled: boolean;
}
@@ -36,6 +46,7 @@ const OrganizationURLConfig: FunctionComponent<Props> = ({ disabled }) => (
>
{({ input, meta }) => (
<TextFieldWithValidation
{...input}
id={`configure-organization-${input.name}`}
disabled={disabled}
autoComplete="off"
@@ -44,7 +55,6 @@ const OrganizationURLConfig: FunctionComponent<Props> = ({ disabled }) => (
spellCheck={false}
fullWidth
meta={meta}
{...input}
/>
)}
</Field>
@@ -1,38 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { OrganizationURLConfigContainer_settings as SettingsData } from "coral-admin/__generated__/OrganizationURLConfigContainer_settings.graphql";
import OrganizationURLConfig from "./OrganizationURLConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class OrganizationURLConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <OrganizationURLConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment OrganizationURLConfigContainer_settings on Settings {
organization {
url
}
}
`,
})(OrganizationURLConfigContainer);
export default enhanced;
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import {
FormField,
@@ -15,6 +16,15 @@ import WordListTextArea from "./WordListTextArea";
import styles from "./BannedWordListConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment BannedWordListConfig_formValues on Settings {
wordList {
banned
}
}
`;
interface Props {
disabled: boolean;
}
@@ -1,38 +0,0 @@
import React from "react";
import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { BannedWordListConfigContainer_settings as SettingsData } from "coral-admin/__generated__/BannedWordListConfigContainer_settings.graphql";
import BannedWordListConfig from "./BannedWordListConfig";
interface Props {
settings: SettingsData;
onInitValues: (values: SettingsData) => void;
disabled: boolean;
}
class BannedWordListConfigContainer extends React.Component<Props> {
constructor(props: Props) {
super(props);
props.onInitValues(props.settings);
}
public render() {
const { disabled } = this.props;
return <BannedWordListConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment BannedWordListConfigContainer_settings on Settings {
wordList {
banned
}
}
`,
})(BannedWordListConfigContainer);
export default enhanced;
@@ -1,5 +1,6 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";
import {
FormField,
@@ -15,6 +16,15 @@ import WordListTextArea from "./WordListTextArea";
import styles from "./SuspectWordListConfig.css";
// eslint-disable-next-line no-unused-expressions
graphql`
fragment SuspectWordListConfig_formValues on Settings {
wordList {
suspect
}
}
`;
interface Props {
disabled: boolean;
}

Some files were not shown because too many files have changed in this diff Show More