mirror of
https://github.com/wassname/talk.git
synced 2026-09-10 12:43:11 +08:00
[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:
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user