[CORL-423] Rearrange client files and folder (#2352)

* chore: reorganize account

* chore: reorganize install

* chore: reorganize auth files

* chore: reorganize admin files

* fix: graphql naming

* chore: adapt account routing
This commit is contained in:
Vinh
2019-06-11 15:33:46 +00:00
committed by Wyatt Johnson
parent 05c8f06ed8
commit 3947b143cb
559 changed files with 1691 additions and 1755 deletions
+25
View File
@@ -0,0 +1,25 @@
/* Here we add global stylings for body and document */
:global {
body {
margin: 0;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
background-color: var(--palette-background-light);
color: var(--palette-text-primary);
}
}
.root {
width: 100%;
box-sizing: border-box;
}
.container {
padding: var(--mini-unit) var(--mini-unit) calc(2 * var(--mini-unit))
var(--mini-unit);
max-width: 1080px;
margin: 0 auto;
}
+10
View File
@@ -0,0 +1,10 @@
import React from "react";
import { createRenderer } from "react-test-renderer/shallow";
import App from "./App";
it("renders correctly", () => {
const renderer = createRenderer();
renderer.render(<App />);
expect(renderer.getRenderOutput()).toMatchSnapshot();
});
+21
View File
@@ -0,0 +1,21 @@
import React, { Component } from "react";
import InstallWizard from "./InstallWizard";
import MainBar from "./MainBar";
import styles from "./App.css";
class App extends Component {
public render() {
return (
<div className={styles.root}>
<MainBar />
<div className={styles.container}>
<InstallWizard />
</div>
</div>
);
}
}
export default App;
+20
View File
@@ -0,0 +1,20 @@
.root {
padding: calc(4 * var(--mini-unit)) 0;
}
.headline {
font-size: 1.2rem;
}
.headlineMain {
font-weight: bold;
font-size: 1.5rem;
}
.subHeadline {
font-size: 1.5rem;
}
.subHeadlineMain {
font-size: 2rem;
}
+42
View File
@@ -0,0 +1,42 @@
import cn from "classnames";
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Flex, Typography } from "coral-ui/components";
import styles from "./Header.css";
interface HeaderProps {
main?: boolean;
}
const Header: FunctionComponent<HeaderProps> = ({ main }) => {
return (
<Flex
alignItems="center"
justifyContent="center"
direction="column"
className={styles.root}
>
<Typography
className={cn(styles.headline, {
[styles.headlineMain]: main,
})}
>
The Coral Project
</Typography>
<Localized id="install-header-title">
<Typography
className={cn(styles.subHeadline, {
[styles.subHeadlineMain]: main,
})}
variant="heading1"
>
Coral Installation Wizard
</Typography>
</Localized>
</Flex>
);
};
export default Header;
@@ -0,0 +1,17 @@
import { Environment } from "relay-runtime";
import { CoralContext } from "coral-framework/lib/bootstrap";
import { createMutationContainer } from "coral-framework/lib/relay";
import { install, InstallInput } from "coral-framework/rest";
export type InstallMutation = (input: InstallInput) => Promise<void>;
export async function commit(
environment: Environment,
input: InstallInput,
{ rest }: CoralContext
) {
await install(rest, input);
}
export const withInstallMutation = createMutationContainer("install", commit);
@@ -0,0 +1,126 @@
import React, { Component } from "react";
import { InstallInput } from "coral-framework/rest";
import { InstallMutation, withInstallMutation } from "./InstallMutation";
import AddOrganizationStep from "./steps/AddOrganizationStep";
import CreateYourAccountStep from "./steps/CreateYourAccountStep";
import FinalStep from "./steps/FinalStep";
import InitialStep from "./steps/InitialStep";
import PermittedDomainsStep from "./steps/PermittedDomainsStep";
import Wizard from "./Wizard";
interface FormData {
organizationName: string;
organizationContactEmail: string;
organizationURL: string;
email: string;
username: string;
password: string;
confirmPassword: string;
allowedDomains: string[];
}
interface InstallWizardState {
step: number;
data: FormData;
}
function shapeFinalData(data: FormData): InstallInput {
const {
organizationName,
organizationContactEmail,
organizationURL,
allowedDomains,
username,
password,
email,
} = data;
return {
tenant: {
organization: {
name: organizationName,
contactEmail: organizationContactEmail,
url: organizationURL,
},
allowedDomains,
},
user: {
username,
password,
email,
},
};
}
interface Props {
install: InstallMutation;
}
class InstallWizard extends Component<Props, InstallWizardState> {
public state = {
step: 0,
data: {
organizationContactEmail: "",
organizationName: "",
organizationURL: "",
email: "",
username: "",
password: "",
confirmPassword: "",
allowedDomains: [],
},
};
private handleSaveData = (newData: FormData) => {
this.setState(({ data }) => ({
data: { ...data, ...newData },
}));
};
private handleGoToNextStep = () =>
this.setState(({ step }) => ({
step: step + 1,
}));
private handleGoToPreviousStep = () =>
this.setState(({ step }) => ({
step: step - 1,
}));
private handleInstall = async (newData: Partial<FormData>) => {
return await this.props.install(
shapeFinalData({ ...this.state.data, ...newData })
);
};
public render() {
return (
<Wizard currentStep={this.state.step}>
<InitialStep onGoToNextStep={this.handleGoToNextStep} />
<CreateYourAccountStep
data={this.state.data}
onSaveData={this.handleSaveData}
onGoToNextStep={this.handleGoToNextStep}
onGoToPreviousStep={this.handleGoToPreviousStep}
/>
<AddOrganizationStep
data={this.state.data}
onSaveData={this.handleSaveData}
onGoToNextStep={this.handleGoToNextStep}
onGoToPreviousStep={this.handleGoToPreviousStep}
/>
<PermittedDomainsStep
data={this.state.data}
onGoToNextStep={this.handleGoToNextStep}
onGoToPreviousStep={this.handleGoToPreviousStep}
onInstall={this.handleInstall}
/>
<FinalStep />
</Wizard>
);
}
}
export default withInstallMutation(InstallWizard);
+26
View File
@@ -0,0 +1,26 @@
.root {
width: 100%;
border-bottom: 2px solid var(--palette-grey-main);
padding: var(--mini-unit);
box-sizing: border-box;
background: var(--palette-text-primary);
background: linear-gradient(
to right,
var(--palette-text-primary) 0%,
var(--palette-grey-main) 100%
);
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14),
0 1px 10px 0 rgba(0, 0, 0, 0.12);
}
.container {
max-width: 1080px;
margin: 0 auto;
display: flex;
justify-content: flex-start;
box-sizing: border-box;
}
.title {
color: var(--palette-text-light);
}
+19
View File
@@ -0,0 +1,19 @@
import React from "react";
import { Typography } from "coral-ui/components";
import styles from "./MainBar.css";
const MainBar = () => {
return (
<div className={styles.root}>
<div className={styles.container}>
<Typography variant="heading1" className={styles.title}>
Coral
</Typography>
</div>
</div>
);
};
export default MainBar;
+13
View File
@@ -0,0 +1,13 @@
.root {
max-width: 600px;
margin: 0 auto;
}
.section {
max-width: 400px;
margin: 0 auto;
}
.stepBar {
padding: var(--mini-unit) 0 calc(6 * var(--mini-unit));
}
+57
View File
@@ -0,0 +1,57 @@
import cn from "classnames";
import { Localized } from "fluent-react/compat";
import React, { Component, ReactNode } from "react";
import { Step, StepBar } from "coral-ui/components";
import Header from "./Header";
import { WizardProps } from "./Wizard";
import styles from "./Wizard.css";
export interface WizardProps {
currentStep: number;
className?: string;
children: ReactNode;
}
class Wizard extends Component<WizardProps> {
public render() {
const { children, currentStep, className } = this.props;
const wizardChildren = React.Children.toArray(children);
const wizardChildrenToRender = wizardChildren.filter(
(_, i) => i === currentStep
);
return (
<div className={cn(className, styles.root)}>
<Header main={currentStep === 0} />
{currentStep !== 0 && currentStep !== wizardChildren.length - 1 && (
<StepBar currentStep={currentStep - 1} className={styles.stepBar}>
<Step hidden>Start</Step>
<Step>
<Localized id="install-createYourAccount-stepTitle">
<span>Create Admin Account</span>
</Localized>
</Step>
<Step>
<Localized id="install-addOrganization-stepTitle">
<span>Add Organization Details</span>
</Localized>
</Step>
<Step>
<Localized id="install-permittedDomains-stepTitle">
<span>Add Permitted Domains</span>
</Localized>
</Step>
<Step hidden>Finish</Step>
</StepBar>
)}
<section className={styles.section}>{wizardChildrenToRender}</section>
</div>
);
}
}
export default Wizard;
@@ -0,0 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<div
className="App-root"
>
<MainBar />
<div
className="App-container"
>
<withContext(createMutationContainer(InstallWizard)) />
</div>
</div>
`;
+1
View File
@@ -0,0 +1 @@
export { default, default as App } from "./App";
@@ -0,0 +1,208 @@
import { Localized } from "fluent-react/compat";
import React from "react";
import { Field, Form } from "react-final-form";
import { OnSubmit } from "coral-framework/lib/form";
import {
composeValidators,
required,
validateEmail,
validateURL,
} from "coral-framework/lib/validation";
import {
CallOut,
Flex,
FormField,
HorizontalGutter,
InputDescription,
InputLabel,
TextField,
Typography,
ValidationMessage,
} from "coral-ui/components";
import BackButton from "./BackButton";
import NextButton from "./NextButton";
interface Props {
onGoToNextStep: () => void;
onGoToPreviousStep: () => void;
data: FormProps;
onSaveData: (newData: FormProps) => void;
}
interface FormProps {
organizationName: string;
organizationContactEmail: string;
organizationURL: string;
}
class AddOrganizationStep extends React.Component<Props> {
private onSubmit: OnSubmit<FormProps> = async (input, form) => {
this.props.onSaveData(input);
return this.props.onGoToNextStep();
};
public render() {
return (
<Form
onSubmit={this.onSubmit}
initialValues={{
organizationName: this.props.data.organizationName,
organizationContactEmail: this.props.data.organizationContactEmail,
organizationURL: this.props.data.organizationURL,
}}
>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="double">
<Localized id="install-addOrganization-title">
<Typography variant="heading1" align="center">
Add Organization
</Typography>
</Localized>
<Localized id="install-addOrganization-description">
<Typography variant="bodyCopy" align="center">
Please tell us the name of your organization. This will appear
in emails when inviting new team members.
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<Field
name="organizationName"
validate={composeValidators(required)}
>
{({ input, meta }) => (
<FormField>
<Localized id="install-addOrganization-orgName">
<InputLabel>Organization Name</InputLabel>
</Localized>
<Localized
id="install-addOrganization-orgNameTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Organization Name"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Field
name="organizationContactEmail"
validate={composeValidators(required, validateEmail)}
>
{({ input, meta }) => (
<FormField>
<Localized id="install-addOrganization-orgEmail">
<InputLabel>Organization Contact Email</InputLabel>
</Localized>
<Localized
id="install-addOrganization-orgEmailTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Organization Contact Email"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Field
name="organizationURL"
validate={composeValidators(required, validateURL)}
>
{({ input, meta }) => (
<FormField>
<Localized id="install-addOrganization-orgURL">
<InputLabel>Organization URL</InputLabel>
</Localized>
<Localized
id="install-addOrganization-orgURLDescription"
strong={<strong />}
>
<InputDescription>
{/* Related: https://github.com/prettier/prettier/issues/2347 */}
Be sure to include <strong>{"http://"}</strong> or{" "}
<strong>{"https://"}</strong> in your URL
</InputDescription>
</Localized>
<Localized
id="install-addOrganization-orgURLTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Organization URL"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Flex direction="row-reverse" itemGutter>
<NextButton submitting={submitting} />
<BackButton
submitting={submitting}
onGoToPreviousStep={this.props.onGoToPreviousStep}
/>
</Flex>
</HorizontalGutter>
</form>
)}
</Form>
);
}
}
export default AddOrganizationStep;
@@ -0,0 +1,30 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Button } from "coral-ui/components";
export interface BackButtonProps {
submitting: boolean;
onGoToPreviousStep: () => void;
}
const BackButton: FunctionComponent<BackButtonProps> = ({
submitting,
onGoToPreviousStep,
}) => {
return (
<Localized id="install-backButton-back">
<Button
onClick={onGoToPreviousStep}
variant="filled"
color="regular"
size="large"
disabled={submitting}
>
Back
</Button>
</Localized>
);
};
export default BackButton;
@@ -0,0 +1,238 @@
import { Localized } from "fluent-react/compat";
import React, { Component } from "react";
import { Field, Form } from "react-final-form";
import { OnSubmit } from "coral-framework/lib/form";
import {
composeValidators,
required,
validateEmail,
validateEqualPasswords,
validatePassword,
validateUsername,
} from "coral-framework/lib/validation";
import {
CallOut,
Flex,
FormField,
HorizontalGutter,
InputDescription,
InputLabel,
TextField,
Typography,
ValidationMessage,
} from "coral-ui/components";
import NextButton from "./NextButton";
interface FormProps {
email: string;
username: string;
password: string;
confirmPassword: string;
}
interface Props {
onGoToNextStep: () => void;
onGoToPreviousStep: () => void;
data: FormProps;
onSaveData: (newData: FormProps) => void;
}
class CreateYourAccountStep extends Component<Props> {
private onSubmit: OnSubmit<FormProps> = async (input, form) => {
this.props.onSaveData(input);
return this.props.onGoToNextStep();
};
public render() {
return (
<Form
onSubmit={this.onSubmit}
initialValues={{
email: this.props.data.email,
username: this.props.data.username,
}}
>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="double">
<Localized id="install-createYourAccount-title">
<Typography variant="heading1" align="center">
Create an Administrator Account
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<Field
name="email"
validate={composeValidators(required, validateEmail)}
>
{({ input, meta }) => (
<FormField>
<Localized id="install-createYourAccount-email">
<InputLabel>Email</InputLabel>
</Localized>
<Localized
id="install-createYourAccount-emailTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Email"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Field
name="username"
validate={composeValidators(required, validateUsername)}
>
{({ input, meta }) => (
<FormField>
<Localized id="install-createYourAccount-username">
<InputLabel>Username</InputLabel>
</Localized>
<Localized id="install-createYourAccount-usernameDescription">
<InputDescription>
An identifier displayed on your comments. You may use
_ and .
</InputDescription>
</Localized>
<Localized
id="install-createYourAccount-usernameTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Username"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Field
name="password"
validate={composeValidators(required, validatePassword)}
>
{({ input, meta }) => (
<FormField>
<Localized id="install-createYourAccount-password">
<InputLabel>Password</InputLabel>
</Localized>
<Localized id="install-createYourAccount-passwordDescription">
<InputDescription>
Must be at least 8 characters
</InputDescription>
</Localized>
<Localized
id="install-createYourAccount-passwordTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Password"
type="password"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Field
name="confirmPassword"
validate={composeValidators(required, validateEqualPasswords)}
>
{({ input, meta }) => (
<FormField>
<Localized id="install-createYourAccount-confirmPassword">
<InputLabel>Confirm Password</InputLabel>
</Localized>
<Localized
id="install-createYourAccount-confirmPasswordTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Confirm Password"
type="password"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Flex direction="row-reverse">
<NextButton submitting={submitting} />
</Flex>
</HorizontalGutter>
</form>
)}
</Form>
);
}
}
export default CreateYourAccountStep;
@@ -0,0 +1,41 @@
import { Localized } from "fluent-react/compat";
import React, { Component } from "react";
import { urls } from "coral-framework/helpers";
import { Button, Flex, Typography } from "coral-ui/components";
class FinalStep extends Component {
public render() {
return (
<Flex direction="column" justifyContent="center" itemGutter="double">
<Localized id="install-finalStep-description">
<Typography variant="bodyCopy">
Thanks for installing Coral! We sent an email to verify your email
address. While you finish setting up the account, you can start
engaging with your readers now.
</Typography>
</Localized>
<Flex direction="row" itemGutter justifyContent="center">
<Localized id="install-finalStep-goToTheDocs">
<Button
anchor
color="regular"
variant="filled"
href="https://docs.coralproject.net"
target="_blank"
>
Go to the Docs
</Button>
</Localized>
<Localized id="install-finalStep-goToAdmin">
<Button anchor color="primary" variant="filled" href={urls.admin}>
Go to Admin
</Button>
</Localized>
</Flex>
</Flex>
);
}
}
export default FinalStep;
@@ -0,0 +1,38 @@
import { Localized } from "fluent-react/compat";
import React, { Component } from "react";
import { Button, Flex, Typography } from "coral-ui/components";
interface InitialStepProps {
onGoToNextStep: () => void;
}
class InitialStep extends Component<InitialStepProps> {
public render() {
return (
<Flex direction="column" justifyContent="center" itemGutter="double">
<Localized id="install-initialStep-copy">
<Typography variant="bodyCopy">
The remainder of the Coral installation will take about ten minutes.
Once you complete the following three steps, you will have a free
installation and provision Mongo and Redis.
</Typography>
</Localized>
<Flex justifyContent="center">
<Localized id="install-initialStep-getStarted">
<Button
onClick={this.props.onGoToNextStep}
color="primary"
variant="filled"
fullWidth={false}
>
Get Started
</Button>
</Localized>
</Flex>
</Flex>
);
}
}
export default InitialStep;
@@ -0,0 +1,27 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import { Button, ButtonIcon } from "coral-ui/components";
export interface NextButtonProps {
submitting: boolean;
}
const NextButton: FunctionComponent<NextButtonProps> = props => {
return (
<Button
variant="filled"
color="primary"
size="large"
type="submit"
disabled={props.submitting}
>
<Localized id="install-nextButton-next">
<span>Next</span>
</Localized>
<ButtonIcon>arrow_forward</ButtonIcon>
</Button>
);
};
export default NextButton;
@@ -0,0 +1,136 @@
import { OnSubmit } from "coral-framework/lib/form";
import { FORM_ERROR } from "final-form";
import { Localized } from "fluent-react/compat";
import React, { Component } from "react";
import { Field, Form } from "react-final-form";
import {
Button,
CallOut,
Flex,
FormField,
HorizontalGutter,
InputDescription,
InputLabel,
TextField,
Typography,
ValidationMessage,
} from "coral-ui/components";
import BackButton from "./BackButton";
interface FormProps {
allowedDomains: string;
}
interface Props {
onGoToNextStep: () => void;
onGoToPreviousStep: () => void;
data: { allowedDomains: string[] };
onInstall: (newData: { allowedDomains: string[] }) => Promise<void>;
}
class PermittedDomainsStep extends Component<Props> {
private onSubmit: OnSubmit<FormProps> = async (input, form) => {
try {
const allowedDomains = input.allowedDomains.split(",");
await this.props.onInstall({ allowedDomains });
return this.props.onGoToNextStep();
} catch (error) {
return { [FORM_ERROR]: error.message };
}
};
public render() {
return (
<Form
onSubmit={this.onSubmit}
initialValues={{
allowedDomains: this.props.data.allowedDomains.join(","),
}}
>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="double">
<Localized id="install-permittedDomains-title">
<Typography variant="heading1" align="center">
Permitted Domains
</Typography>
</Localized>
<Localized id="install-permittedDomains-description">
<Typography variant="bodyCopy" align="center">
Enter the domains you would like to permit for Coral, e.g.
your local, staging and production environments (ex.
localhost:3000, staging.domain.com, domain.com).
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<Field name="allowedDomains">
{({ input, meta }) => (
<FormField>
<Localized id="install-permittedDomains-permittedDomains">
<InputLabel>Permitted Domains</InputLabel>
</Localized>
<Localized id="install-permittedDomains-permittedDomainsDescription">
<InputDescription>
Insert domains separated by comma
</InputDescription>
</Localized>
<Localized
id="install-permittedDomains-permittedDomainsTextField"
attrs={{ placeholder: true }}
>
<TextField
name={input.name}
onChange={input.onChange}
value={input.value}
placeholder="Domains"
color={
meta.touched && (meta.error || meta.submitError)
? "error"
: "regular"
}
disabled={submitting}
fullWidth
/>
</Localized>
{meta.touched && (meta.error || meta.submitError) && (
<ValidationMessage fullWidth>
{meta.error || meta.submitError}
</ValidationMessage>
)}
</FormField>
)}
</Field>
<Flex direction="row-reverse" itemGutter>
<Localized id="install-permittedDomains-finishInstall">
<Button
variant="filled"
color="primary"
size="large"
type="submit"
disabled={submitting}
>
Finish Install
</Button>
</Localized>
<BackButton
submitting={submitting}
onGoToPreviousStep={this.props.onGoToPreviousStep}
/>
</Flex>
</HorizontalGutter>
</form>
)}
</Form>
);
}
}
export default PermittedDomainsStep;