Farmbot-Web-App/frontend/front_page/front_page.tsx

265 lines
8.0 KiB
TypeScript

import * as React from "react";
import axios from "axios";
import { error as log, success, init as logInit } from "../toast/toast";
import { AuthState } from "../auth/interfaces";
import { prettyPrintApiErrors, attachToRoot } from "../util";
import { API } from "../api";
import { Session } from "../session";
import { FrontPageState, SetterCB } from "./interfaces";
import { Row, Col } from "../ui/index";
import { LoginProps, Login } from "./login";
import { ForgotPassword, ForgotPasswordProps } from "./forgot_password";
import { ResendVerification } from "./resend_verification";
import { CreateAccount } from "./create_account";
import { Content } from "../constants";
import { LaptopSplash } from "./laptop_splash";
import { TermsCheckbox } from "./terms_checkbox";
import { get } from "lodash";
import { t } from "../i18next_wrapper";
export const attachFrontPage =
() => attachToRoot(FrontPage, {});
const showFor = (size: string[], extraClass?: string): string => {
const ALL_SIZES = ["xs", "sm", "md", "lg", "xl"];
const HIDDEN_SIZES = ALL_SIZES.filter(x => !size.includes(x));
const classNames = HIDDEN_SIZES.map(x => "hidden-" + x);
if (extraClass) { classNames.push(extraClass); }
return classNames.join(" ");
};
export interface PartialFormEvent {
currentTarget: {
checked: boolean;
defaultValue: string;
value: string;
}
}
/** Set value for front page state field (except for "activePanel"). */
export const setField =
(name: keyof Omit<FrontPageState, "activePanel">, cb: SetterCB) =>
(event: PartialFormEvent) => {
const state: Partial<FrontPageState> = {};
switch (name) {
// Booleans
case "agreeToTerms":
case "registrationSent":
state[name] = event.currentTarget.checked;
break;
// all others (string)
default:
state[name] = event.currentTarget.value;
}
cb(state);
};
export class FrontPage extends React.Component<{}, Partial<FrontPageState>> {
constructor(props: {}) {
super(props);
this.state = {
registrationSent: false,
regEmail: "",
regName: "",
regPassword: "",
regConfirmation: "",
email: "",
loginPassword: "",
agreeToTerms: false,
activePanel: "login"
};
}
componentDidMount() {
if (Session.fetchStoredToken()) { window.location.assign("/app/controls"); }
logInit();
API.setBaseUrl(API.fetchBrowserLocation());
this.setState({});
}
submitLogin = (e: React.FormEvent<{}>) => {
e.preventDefault();
const { email, loginPassword } = this.state;
const payload = { user: { email, password: loginPassword } };
API.setBaseUrl(API.fetchBrowserLocation());
axios.post<AuthState>(API.current.tokensPath, payload)
.then(resp => {
Session.replaceToken(resp.data);
window.location.assign("/app/controls");
}).catch((error: Error) => {
switch (get(error, "response.status")) {
case 451: // TOS was updated; User must agree to terms.
window.location.assign("/tos_update");
break;
case 403: // User did not click verification email link.
log(t("Account Not Verified"));
this.setState({ activePanel: "resendVerificationEmail" });
break;
default:
log(prettyPrintApiErrors(error as {}));
}
this.setState({ loginPassword: "" });
});
}
submitRegistration = (e: React.FormEvent<{}>) => {
e.preventDefault();
const {
regEmail,
regName,
regPassword,
regConfirmation,
agreeToTerms
} = this.state;
const form = {
user: {
name: regName,
email: regEmail,
password: regPassword,
password_confirmation: regConfirmation,
agree_to_terms: agreeToTerms
}
};
axios.post(API.current.usersPath, form).then(() => {
const m = "Almost done! Check your email for the verification link.";
success(t(m), t("Success"));
this.setState({ registrationSent: true });
}).catch(error => {
log(prettyPrintApiErrors(error));
});
}
toggleForgotPassword = () => this.setState({ activePanel: "forgotPassword" });
submitForgotPassword = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const { email } = this.state;
const data = { email };
axios.post(API.current.passwordResetPath, data)
.then(() => {
success(t("Email has been sent."), t("Forgot Password"));
this.setState({ activePanel: "login" });
}).catch(error => {
let errorMessage = prettyPrintApiErrors(error);
if (errorMessage.toLowerCase().includes("not found")) {
errorMessage =
`That email address is not associated with an account.`;
}
log(t(errorMessage));
});
}
handleFormUpdate: SetterCB = (state) => this.setState(state);
maybeRenderTos = () => {
if (globalConfig.TOS_URL) {
return <TermsCheckbox
privUrl={globalConfig.PRIV_URL}
tosUrl={globalConfig.TOS_URL}
onChange={setField("agreeToTerms", this.handleFormUpdate)}
agree={this.state.agreeToTerms} />;
}
}
loginPanel = () => {
const props: LoginProps = {
email: this.state.email || "",
onEmailChange: setField("email", this.handleFormUpdate),
onLoginPasswordChange: setField("loginPassword", this.handleFormUpdate),
onToggleForgotPassword: this.toggleForgotPassword,
onSubmit: this.submitLogin,
};
return <Login {...props} />;
}
forgotPasswordPanel = () => {
const goBack = () => this.setState({ activePanel: "login" });
const props: ForgotPasswordProps = {
onGoBack: goBack,
onSubmit: this.submitForgotPassword,
email: this.state.email || "",
onEmailChange: setField("email", this.handleFormUpdate),
};
return <ForgotPassword {...props} />;
}
resendVerificationPanel = () => {
const goBack = () => this.setState({ activePanel: "login" });
return <ResendVerification
onGoBack={goBack}
ok={() => {
success(t(Content.VERIFICATION_EMAIL_RESENT));
goBack();
}}
no={() => {
log(t(Content.VERIFICATION_EMAIL_RESEND_ERROR));
goBack();
}}
email={this.state.email || ""} />;
}
activePanel = () => {
switch (this.state.activePanel) {
case "forgotPassword": return this.forgotPasswordPanel();
case "resendVerificationEmail": return this.resendVerificationPanel();
case "login":
default:
return this.loginPanel();
}
}
defaultContent() {
return <div className="static-page">
<Row>
<Col xs={12}>
<h1 className="text-center">
{t("Welcome to the")}
<br className={showFor(["xs"])} />
&nbsp;
{t("FarmBot Web App")}
</h1>
</Col>
</Row>
<div className="inner-width">
<Row>
<h2 className="text-center">
<Col xs={12}>
{t("Setup, customize, and control FarmBot from your")}
&nbsp;
<span className={showFor(["md", "lg", "xl"])}>
{t("computer")}
</span>
<span className={showFor(["sm"])}>
{t("tablet")}
</span>
<span className={showFor(["xs"])}>
{t("smartphone")}
</span>
</Col>
</h2>
</Row>
<LaptopSplash className={showFor(["md", "lg", "xl"], "col-md-7")} />
<img
className={showFor(["sm"], "col-md-7")}
src="/app-resources/img/farmbot-tablet.png" />
<Row>
<this.activePanel />
<CreateAccount
submitRegistration={this.submitRegistration}
sent={!!this.state.registrationSent}
get={(key) => this.state[key]}
set={(key, val) => this.setState({ [key]: val })}>
{this.maybeRenderTos()}
</CreateAccount>
</Row>
</div>
</div>;
}
render() { return Session.fetchStoredToken() ? <div /> : this.defaultContent(); }
}