feat: initial implementation (#2409)

This commit is contained in:
Wyatt Johnson
2019-07-23 17:20:40 +00:00
committed by GitHub
parent fc464bd7c9
commit b1732f8a00
24 changed files with 646 additions and 70 deletions
+2
View File
@@ -10,6 +10,7 @@ import ConfigureRoute from "./routes/Configure";
import {
AdvancedConfigRoute,
AuthConfigRoute,
EmailConfigRoute,
GeneralConfigRoute,
ModerationConfigRoute,
OrganizationConfigRoute,
@@ -71,6 +72,7 @@ export default makeRouteConfig(
<Route path="wordList" {...WordListConfigRoute.routeConfig} />
<Route path="auth" {...AuthConfigRoute.routeConfig} />
<Route path="advanced" {...AdvancedConfigRoute.routeConfig} />
<Route path="email" {...EmailConfigRoute.routeConfig} />
</Route>
</Route>
</Route>
@@ -49,6 +49,9 @@ const Configure: FunctionComponent<Props> = ({
<Localized id="configure-sideBarNavigation-authentication">
<Link to="/admin/configure/auth">Authentication</Link>
</Localized>
<Localized id="configure-sideBarNavigation-email">
<Link to="/admin/configure/email">Email</Link>
</Localized>
<Localized id="configure-sideBarNavigation-advanced">
<Link to="/admin/configure/advanced">Advanced</Link>
</Localized>
@@ -0,0 +1,3 @@
.title {
display: flex;
}
@@ -0,0 +1,123 @@
import { FormApi } from "final-form";
import { Localized } from "fluent-react/compat";
import { RouteProps } from "found";
import React from "react";
import { Field } from "react-final-form";
import { graphql } from "react-relay";
import { EmailConfigContainer_email } from "coral-admin/__generated__/EmailConfigContainer_email.graphql";
import { DeepNullable, DeepPartial } from "coral-common/types";
import { pureMerge } from "coral-common/utils";
import { parseBool } from "coral-framework/lib/form";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { GQLEmailConfiguration } from "coral-framework/schema";
import {
CheckBox,
Flex,
FormField,
HorizontalGutter,
} from "coral-ui/components";
import Header from "../../Header";
import FromContainer from "./FromContainer";
import SMTPContainer from "./SMTPContainer";
import styles from "./EmailConfigContainer.css";
interface Props {
form: FormApi;
submitting: boolean;
email: EmailConfigContainer_email;
}
export type FormProps = DeepNullable<GQLEmailConfiguration>;
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({ email: this.initialValues });
}
private handleOnInitValues: OnInitValuesFct = values => {
if (values.smtp && values.smtp.authentication === null) {
values = { ...values, smtp: { ...values.smtp, authentication: true } };
}
if (values.smtp && values.smtp.secure === null) {
values = { ...values, smtp: { ...values.smtp, secure: true } };
}
this.initialValues = pureMerge<DeepPartial<FormProps>>(
this.initialValues,
values
);
};
public render() {
const { email, submitting } = this.props;
return (
<HorizontalGutter size="double">
<Field name="email.enabled" type="checkbox" parse={parseBool}>
{({ input }) => (
<Header
className={styles.title}
container={<Flex justifyContent="space-between" />}
>
<div>
<Localized id="configure-email">
<span>Email settings</span>
</Localized>
</div>
<div>
<FormField>
<Localized id="configure-email-configBoxEnabled">
<CheckBox
id={input.name}
name={input.name}
onChange={input.onChange}
checked={input.value}
disabled={submitting}
>
Enabled
</CheckBox>
</Localized>
</FormField>
</div>
</Header>
)}
</Field>
<Field name="email.enabled" subscription={{ value: true }}>
{({ input: { value } }) => (
<>
<FromContainer
email={email}
disabled={submitting || !value}
onInitValues={this.handleOnInitValues}
/>
<SMTPContainer
email={email}
disabled={submitting || !value}
onInitValues={this.handleOnInitValues}
/>
</>
)}
</Field>
</HorizontalGutter>
);
}
}
const enhanced = withFragmentContainer<Props>({
email: graphql`
fragment EmailConfigContainer_email on EmailConfiguration {
enabled
...FromContainer_email
...SMTPContainer_email
}
`,
})(EmailConfigContainer);
export default enhanced;
@@ -0,0 +1,49 @@
import { FormApi } from "final-form";
import React from "react";
import { graphql } from "react-relay";
import { EmailConfigRouteQueryResponse } from "coral-admin/__generated__/EmailConfigRouteQuery.graphql";
import { withRouteConfig } from "coral-framework/lib/router";
import { Delay, Spinner } from "coral-ui/components";
import EmailConfigContainer from "./EmailConfigContainer";
interface Props {
data: EmailConfigRouteQueryResponse | null;
form: FormApi;
submitting: boolean;
}
class EmailConfigRoute extends React.Component<Props> {
public render() {
if (!this.props.data) {
return (
<Delay>
<Spinner />
</Delay>
);
}
return (
<EmailConfigContainer
email={this.props.data.settings.email}
form={this.props.form}
submitting={this.props.submitting}
/>
);
}
}
const enhanced = withRouteConfig<Props>({
query: graphql`
query EmailConfigRouteQuery {
settings {
email {
...EmailConfigContainer_email
}
}
}
`,
cacheConfig: { force: true },
})(EmailConfigRoute);
export default enhanced;
@@ -0,0 +1,79 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import { validateEmail } from "coral-framework/lib/validation";
import {
FieldSet,
FormField,
HorizontalGutter,
InputDescription,
InputLabel,
TextField,
ValidationMessage,
} from "coral-ui/components";
interface Props {
disabled: boolean;
}
const From: FunctionComponent<Props> = ({ disabled }) => (
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
<FormField>
<Localized id="configure-email-fromNameLabel">
<InputLabel>From name</InputLabel>
</Localized>
<Localized id="configure-email-fromNameDescription">
<InputDescription>
Name as it will appear on all outgoing emails
</InputDescription>
</Localized>
<Field name="email.fromName">
{({ input, meta }) => (
<>
<TextField fullWidth disabled={disabled} {...input} />
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
</FormField>
<FormField>
<Localized id="configure-email-fromEmailLabel">
<InputLabel>From email address</InputLabel>
</Localized>
<Localized id="configure-email-fromEmailDescription">
<InputDescription>
Email address that will be used to send messages
</InputDescription>
</Localized>
<Field name="email.fromEmail" validate={validateEmail}>
{({ input, meta }) => (
<>
<TextField
type="email"
fullWidth
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={disabled}
{...input}
/>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
</FormField>
</HorizontalGutter>
);
export default From;
@@ -0,0 +1,38 @@
import React from "react";
import { graphql } from "react-relay";
import { FromContainer_email } from "coral-admin/__generated__/FromContainer_email.graphql";
import { withFragmentContainer } from "coral-framework/lib/relay";
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(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;
@@ -0,0 +1,149 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Field } from "react-final-form";
import {
FieldSet,
FormField,
HorizontalGutter,
InputDescription,
InputLabel,
PasswordField,
TextField,
ValidationMessage,
} from "coral-ui/components";
import OnOffField from "../../OnOffField";
import Subheader from "../../Subheader";
interface Props {
disabled: boolean;
}
const SMTP: FunctionComponent<Props> = ({ disabled }) => (
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
<FormField>
<Localized id="configure-email-smtpHostLabel">
<InputLabel>SMTP host</InputLabel>
</Localized>
<Localized id="configure-email-smtpHostDescription">
<InputDescription>(ex. smtp.sendgrid.com)</InputDescription>
</Localized>
<Field name="email.smtp.host">
{({ input, meta }) => (
<>
<TextField
id={input.name}
fullWidth
disabled={disabled}
{...input}
/>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
</FormField>
<FormField>
<Localized id="configure-email-smtpPortLabel">
<InputLabel>SMTP port</InputLabel>
</Localized>
<Localized id="configure-email-smtpPortDescription">
<InputDescription>(ex. 25)</InputDescription>
</Localized>
<Field name="email.smtp.port">
{({ input, meta }) => (
<>
<TextField
id={input.name}
type="number"
fullWidth
disabled={disabled}
{...input}
/>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
</FormField>
<FormField>
<Localized id="configure-email-smtpTLSLabel">
<InputLabel>TLS</InputLabel>
</Localized>
<OnOffField name="email.smtp.secure" disabled={disabled} />
</FormField>
<FormField>
<Localized id="configure-email-smtpAuthenticationLabel">
<InputLabel>SMTP Authentication</InputLabel>
</Localized>
<OnOffField name="email.smtp.authentication" disabled={disabled} />
</FormField>
<Field name="email.smtp.authentication" subscription={{ value: true }}>
{({ input: { value: enabled } }) => (
<>
<Localized id="configure-email-smtpCredentialsHeader">
<Subheader>Email credentials</Subheader>
</Localized>
<FormField>
<Localized id="configure-email-smtpUsernameLabel">
<InputLabel>Username</InputLabel>
</Localized>
<Field name="email.smtp.username">
{({ input, meta }) => (
<>
<TextField
id={input.name}
fullWidth
disabled={disabled || !enabled}
{...input}
/>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
</FormField>
<FormField>
<Localized id="configure-email-smtpPasswordLabel">
<InputLabel>Password</InputLabel>
</Localized>
<Field name="email.smtp.password">
{({ input, meta }) => (
<>
<PasswordField
id={input.name}
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
{...input}
disabled={disabled || !enabled}
fullWidth
/>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</>
)}
</Field>
</FormField>
</>
)}
</Field>
</HorizontalGutter>
);
export default SMTP;
@@ -0,0 +1,44 @@
import React from "react";
import { graphql } from "react-relay";
import { SMTPContainer_email } from "coral-admin/__generated__/SMTPContainer_email.graphql";
import { withFragmentContainer } from "coral-framework/lib/relay";
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(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;
@@ -0,0 +1 @@
export { default, default as EmailConfigRoute } from "./EmailConfigRoute";
@@ -1,5 +1,6 @@
export { AdvancedConfigRoute } from "./Advanced";
export { AuthConfigRoute } from "./Auth";
export { EmailConfigRoute } from "./Email";
export { GeneralConfigRoute } from "./General";
export { ModerationConfigRoute } from "./Moderation";
export { OrganizationConfigRoute } from "./Organization";
@@ -70,6 +70,15 @@ exports[`renders configure advanced 1`] = `
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/email"
onClick={[Function]}
>
Email
</a>
</li>
<li>
<a
className="Link-link Link-linkActive"
@@ -1936,6 +1936,15 @@ exports[`renders configure auth 1`] = `
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/email"
onClick={[Function]}
>
Email
</a>
</li>
<li>
<a
className="Link-link"
@@ -70,6 +70,15 @@ exports[`renders configure general 1`] = `
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/email"
onClick={[Function]}
>
Email
</a>
</li>
<li>
<a
className="Link-link"
@@ -70,6 +70,15 @@ exports[`renders configure moderation 1`] = `
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/email"
onClick={[Function]}
>
Email
</a>
</li>
<li>
<a
className="Link-link"
@@ -70,6 +70,15 @@ exports[`renders configure organization 1`] = `
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/email"
onClick={[Function]}
>
Email
</a>
</li>
<li>
<a
className="Link-link"
@@ -70,6 +70,15 @@ exports[`renders configure wordList 1`] = `
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/email"
onClick={[Function]}
>
Email
</a>
</li>
<li>
<a
className="Link-link"