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

268 lines
8.1 KiB
TypeScript
Raw Permalink Normal View History

2017-06-29 12:54:02 -06:00
import * as React from "react";
import axios from "axios";
2019-06-24 15:39:49 -06:00
import { error as log, success, init as logInit } from "../toast/toast";
2017-06-29 12:54:02 -06:00
import { AuthState } from "../auth/interfaces";
2018-11-02 09:05:54 -06:00
import { prettyPrintApiErrors, attachToRoot } from "../util";
2017-06-29 12:54:02 -06:00
import { API } from "../api";
import { Session } from "../session";
import { FrontPageState, SetterCB } from "./interfaces";
import { Row, Col } from "../ui/index";
import { LoginProps, Login } from "./login";
2017-08-28 09:05:31 -06:00
import { ForgotPassword, ForgotPasswordProps } from "./forgot_password";
import { ResendVerification } from "./resend_verification";
import { CreateAccount } from "./create_account";
import { Content } from "../constants";
2018-11-30 19:41:50 -07:00
import { LaptopSplash } from "./laptop_splash";
import { TermsCheckbox } from "./terms_checkbox";
2019-02-04 07:32:26 -07:00
import { get } from "lodash";
2019-04-02 13:59:37 -06:00
import { t } from "../i18next_wrapper";
2017-08-09 10:13:04 -06:00
2019-01-29 07:16:44 -07:00
export const attachFrontPage =
() => attachToRoot(FrontPage, {});
2018-11-02 09:05:54 -06:00
2018-07-31 15:22:05 -06:00
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(" ");
};
2018-11-30 19:41:50 -07:00
export interface PartialFormEvent {
currentTarget: {
checked: boolean;
defaultValue: string;
value: string;
}
}
2019-06-03 12:46:58 -06:00
/** Set value for front page state field (except for "activePanel"). */
export const setField =
2020-02-28 09:34:28 -07:00
(field: keyof Omit<FrontPageState, "activePanel">, cb: SetterCB) =>
2019-06-03 12:46:58 -06:00
(event: PartialFormEvent) => {
const state: Partial<FrontPageState> = {};
2018-06-15 07:37:13 -06:00
2020-02-28 09:34:28 -07:00
switch (field) {
2019-06-03 12:46:58 -06:00
// Booleans
case "agreeToTerms":
case "registrationSent":
2020-02-28 09:34:28 -07:00
state[field] = event.currentTarget.checked;
2019-06-03 12:46:58 -06:00
break;
// all others (string)
default:
2020-02-28 09:34:28 -07:00
state[field] = event.currentTarget.value;
2019-06-03 12:46:58 -06:00
}
cb(state);
};
2017-06-29 12:54:02 -06:00
export class FrontPage extends React.Component<{}, Partial<FrontPageState>> {
constructor(props: {}) {
super(props);
2017-06-29 12:54:02 -06:00
this.state = {
registrationSent: false,
2017-06-29 12:54:02 -06:00
regEmail: "",
regName: "",
regPassword: "",
regConfirmation: "",
email: "",
loginPassword: "",
agreeToTerms: false,
activePanel: "login"
2017-06-29 12:54:02 -06:00
};
}
componentDidMount() {
2019-03-11 20:34:49 -06:00
if (Session.fetchStoredToken()) { window.location.assign("/app/controls"); }
2017-06-29 12:54:02 -06:00
logInit();
API.setBaseUrl(API.fetchBrowserLocation());
this.setState({});
2017-06-29 12:54:02 -06:00
}
submitLogin = (e: React.FormEvent<{}>) => {
2017-06-29 12:54:02 -06:00
e.preventDefault();
const { email, loginPassword } = this.state;
2017-08-28 05:49:13 -06:00
const payload = { user: { email, password: loginPassword } };
API.setBaseUrl(API.fetchBrowserLocation());
2018-03-09 22:17:16 -07:00
axios.post<AuthState>(API.current.tokensPath, payload)
.then(resp => {
2017-10-12 14:28:19 -06:00
Session.replaceToken(resp.data);
2019-03-11 20:34:49 -06:00
window.location.assign("/app/controls");
}).catch((error: Error) => {
2019-02-04 07:32:26 -07:00
switch (get(error, "response.status")) {
case 451: // TOS was updated; User must agree to terms.
2018-05-15 20:38:52 -06:00
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 {}));
2017-06-29 12:54:02 -06:00
}
this.setState({ loginPassword: "" });
2017-06-29 12:54:02 -06:00
});
}
submitRegistration = (e: React.FormEvent<{}>) => {
2017-06-29 12:54:02 -06:00
e.preventDefault();
2017-08-28 05:49:13 -06:00
const {
2017-08-07 14:33:38 -06:00
regEmail,
regName,
regPassword,
regConfirmation,
agreeToTerms
} = this.state;
2017-08-28 05:49:13 -06:00
const form = {
2017-06-29 12:54:02 -06:00
user: {
name: regName,
email: regEmail,
password: regPassword,
password_confirmation: regConfirmation,
agree_to_terms: agreeToTerms
}
};
axios.post(API.current.usersPath, form).then(() => {
2017-08-28 05:49:13 -06:00
const m = "Almost done! Check your email for the verification link.";
2019-07-02 11:03:16 -06:00
success(t(m));
this.setState({ registrationSent: true });
2017-06-29 12:54:02 -06:00
}).catch(error => {
log(prettyPrintApiErrors(error));
});
}
toggleForgotPassword = () => this.setState({ activePanel: "forgotPassword" });
2017-08-28 09:05:31 -06:00
submitForgotPassword = (e: React.FormEvent<HTMLFormElement>) => {
2017-06-29 12:54:02 -06:00
e.preventDefault();
2017-08-28 05:49:13 -06:00
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" });
2017-06-29 12:54:02 -06:00
}).catch(error => {
2017-08-11 19:54:06 -06:00
let errorMessage = prettyPrintApiErrors(error);
if (errorMessage.toLowerCase().includes("not found")) {
2017-08-16 10:30:46 -06:00
errorMessage =
`That email address is not associated with an account.`;
2017-08-11 19:54:06 -06:00
}
log(t(errorMessage));
2017-06-29 12:54:02 -06:00
});
}
handleFormUpdate: SetterCB = (state) => this.setState(state);
maybeRenderTos = () => {
2018-11-30 19:41:50 -07:00
if (globalConfig.TOS_URL) {
return <TermsCheckbox
privUrl={globalConfig.PRIV_URL}
tosUrl={globalConfig.TOS_URL}
onChange={setField("agreeToTerms", this.handleFormUpdate)}
agree={this.state.agreeToTerms} />;
2017-06-29 12:54:02 -06:00
}
}
2017-08-28 09:05:31 -06:00
loginPanel = () => {
const props: LoginProps = {
email: this.state.email || "",
onEmailChange: setField("email", this.handleFormUpdate),
onLoginPasswordChange: setField("loginPassword", this.handleFormUpdate),
onToggleForgotPassword: this.toggleForgotPassword,
onSubmit: this.submitLogin,
2017-06-29 12:54:02 -06:00
};
2017-08-28 09:05:31 -06:00
return <Login {...props} />;
}
forgotPasswordPanel = () => {
2017-09-03 00:49:19 -06:00
const goBack = () => this.setState({ activePanel: "login" });
2017-08-28 09:05:31 -06:00
const props: ForgotPasswordProps = {
2017-09-03 00:49:19 -06:00
onGoBack: goBack,
2017-08-28 09:05:31 -06:00
onSubmit: this.submitForgotPassword,
email: this.state.email || "",
onEmailChange: setField("email", this.handleFormUpdate),
2017-08-28 09:05:31 -06:00
};
return <ForgotPassword {...props} />;
}
resendVerificationPanel = () => {
const goBack = () => this.setState({ activePanel: "login" });
return <ResendVerification
2017-09-03 00:49:19 -06:00
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() {
2017-08-28 10:52:03 -06:00
return <div className="static-page">
<Row>
<Col xs={12}>
<h1 className="text-center">
{t("Welcome to the")}
2018-07-31 15:22:05 -06:00
<br className={showFor(["xs"])} />
2017-08-28 10:52:03 -06:00
&nbsp;
{t("FarmBot Web App")}
2017-08-28 10:52:03 -06:00
</h1>
</Col>
</Row>
2017-08-03 14:07:56 -06:00
2017-08-28 10:52:03 -06:00
<div className="inner-width">
<Row>
<h2 className="text-center">
<Col xs={12}>
{t("Setup, customize, and control FarmBot from your")}
&nbsp;
2018-07-31 15:22:05 -06:00
<span className={showFor(["md", "lg", "xl"])}>
2017-08-28 10:52:03 -06:00
{t("computer")}
</span>
2018-07-31 15:22:05 -06:00
<span className={showFor(["sm"])}>
2017-08-28 10:52:03 -06:00
{t("tablet")}
</span>
2018-07-31 15:22:05 -06:00
<span className={showFor(["xs"])}>
2017-08-28 10:52:03 -06:00
{t("smartphone")}
</span>
</Col>
2017-08-28 10:52:03 -06:00
</h2>
</Row>
2018-11-30 19:41:50 -07:00
<LaptopSplash className={showFor(["md", "lg", "xl"], "col-md-7")} />
2017-08-28 10:52:03 -06:00
<img
2018-07-31 15:22:05 -06:00
className={showFor(["sm"], "col-md-7")}
2017-08-28 10:52:03 -06:00
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>
2017-08-28 10:52:03 -06:00
</Row>
</div>
2017-08-28 10:52:03 -06:00
</div>;
2017-06-29 12:54:02 -06:00
}
2020-02-28 09:34:28 -07:00
render() {
return Session.fetchStoredToken()
? <div className={"app-loading"} />
: this.defaultContent();
}
2017-06-29 12:54:02 -06:00
}