mirror of
https://github.com/wassname/talk.git
synced 2026-07-19 11:28:50 +08:00
feat: initial implementation (#2409)
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user