diff --git a/package.json b/package.json index cd80ffb0f7..5d8ddfb278 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "@polymer/paper-input": "^3.2.1", "@polymer/paper-item": "^3.0.1", "@polymer/paper-listbox": "^3.0.1", - "@polymer/paper-ripple": "^3.0.2", "@polymer/paper-slider": "^3.0.1", "@polymer/paper-styles": "^3.0.1", "@polymer/paper-tabs": "^3.1.0", diff --git a/src/data/cloud.ts b/src/data/cloud.ts index 0082c5ee35..a2ddc98c1b 100644 --- a/src/data/cloud.ts +++ b/src/data/cloud.ts @@ -62,6 +62,8 @@ export type CloudStatus = CloudStatusNotLoggedIn | CloudStatusLoggedIn; export interface SubscriptionInfo { human_description: string; + provider: string; + plan_renewal_date?: number; } export interface CloudWebhook { @@ -76,6 +78,39 @@ export interface ThingTalkConversion { placeholders: PlaceholderContainer; } +export const cloudLogin = ( + hass: HomeAssistant, + email: string, + password: string +) => + hass.callApi("POST", "cloud/login", { + email, + password, + }); + +export const cloudLogout = (hass: HomeAssistant) => + hass.callApi("POST", "cloud/logout"); + +export const cloudForgotPassword = (hass: HomeAssistant, email: string) => + hass.callApi("POST", "cloud/forgot_password", { + email, + }); + +export const cloudRegister = ( + hass: HomeAssistant, + email: string, + password: string +) => + hass.callApi("POST", "cloud/register", { + email, + password, + }); + +export const cloudResendVerification = (hass: HomeAssistant, email: string) => + hass.callApi("POST", "cloud/resend_confirm", { + email, + }); + export const fetchCloudStatus = (hass: HomeAssistant) => hass.callWS({ type: "cloud/status" }); diff --git a/src/panels/config/cloud/account/cloud-account.js b/src/panels/config/cloud/account/cloud-account.js deleted file mode 100644 index 9670794dfc..0000000000 --- a/src/panels/config/cloud/account/cloud-account.js +++ /dev/null @@ -1,246 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/paper-item/paper-item-body"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { formatDateTime } from "../../../../common/datetime/format_date_time"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import "../../../../components/buttons/ha-call-api-button"; -import "../../../../components/ha-card"; -import { fetchCloudSubscriptionInfo } from "../../../../data/cloud"; -import "../../../../layouts/hass-subpage"; -import { EventsMixin } from "../../../../mixins/events-mixin"; -import LocalizeMixin from "../../../../mixins/localize-mixin"; -import "../../../../styles/polymer-ha-style"; -import "../../ha-config-section"; -import "./cloud-alexa-pref"; -import "./cloud-google-pref"; -import "./cloud-remote-pref"; -import "./cloud-tts-pref"; -import "./cloud-webhooks"; - -/* - * @appliesMixin EventsMixin - * @appliesMixin LocalizeMixin - */ -class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) { - static get template() { - return html` - - -
- - Home Assistant Cloud -
-

- [[localize('ui.panel.config.cloud.account.thank_you_note')]] -

-
- - - - - - -
- - [[localize('ui.panel.config.cloud.account.manage_account')]] - - [[localize('ui.panel.config.cloud.account.sign_out')]] -
-
-
- - - [[localize('ui.panel.config.cloud.account.integrations')]] -
-

- [[localize('ui.panel.config.cloud.account.integrations_introduction')]] -

-

- [[localize('ui.panel.config.cloud.account.integrations_introduction2')]] - - [[localize('ui.panel.config.cloud.account.integrations_link_all_features')]]. -

-
- - - - - - - - - - -
-
-
- `; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - narrow: Boolean, - cloudStatus: Object, - _subscription: { - type: Object, - value: null, - }, - _rtlDirection: { - type: Boolean, - computed: "_computeRTLDirection(hass)", - }, - }; - } - - ready() { - super.ready(); - this._fetchSubscriptionInfo(); - } - - _computeConnectionStatus(status) { - return status === "connected" - ? this.hass.localize("ui.panel.config.cloud.account.connected") - : status === "disconnected" - ? this.hass.localize("ui.panel.config.cloud.account.not_connected") - : this.hass.localize("ui.panel.config.cloud.account.connecting"); - } - - async _fetchSubscriptionInfo() { - this._subscription = await fetchCloudSubscriptionInfo(this.hass); - if ( - this._subscription.provider && - this.cloudStatus && - this.cloudStatus.cloud !== "connected" - ) { - this.fire("ha-refresh-cloud-status"); - } - } - - handleLogout() { - this.hass - .callApi("post", "cloud/logout") - .then(() => this.fire("ha-refresh-cloud-status")); - } - - _formatSubscription(subInfo) { - if (subInfo === null) { - return this.hass.localize( - "ui.panel.config.cloud.account.fetching_subscription" - ); - } - - let description = subInfo.human_description; - - if (subInfo.plan_renewal_date) { - description = description.replace( - "{periodEnd}", - formatDateTime( - new Date(subInfo.plan_renewal_date * 1000), - this.hass.locale - ) - ); - } - - return description; - } - - _computeRTLDirection(hass) { - return computeRTLDirection(hass); - } -} - -customElements.define("cloud-account", CloudAccount); diff --git a/src/panels/config/cloud/account/cloud-account.ts b/src/panels/config/cloud/account/cloud-account.ts new file mode 100644 index 0000000000..8e98482979 --- /dev/null +++ b/src/panels/config/cloud/account/cloud-account.ts @@ -0,0 +1,268 @@ +import "@material/mwc-button"; +import "@polymer/paper-item/paper-item-body"; +import { LitElement, css, html, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { formatDateTime } from "../../../../common/datetime/format_date_time"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { computeRTLDirection } from "../../../../common/util/compute_rtl"; +import "../../../../components/buttons/ha-call-api-button"; +import "../../../../components/ha-card"; +import { + cloudLogout, + CloudStatusLoggedIn, + fetchCloudSubscriptionInfo, + SubscriptionInfo, +} from "../../../../data/cloud"; +import "../../../../layouts/hass-subpage"; +import { HomeAssistant } from "../../../../types"; +import "../../ha-config-section"; +import "./cloud-alexa-pref"; +import "./cloud-google-pref"; +import "./cloud-remote-pref"; +import "./cloud-tts-pref"; +import "./cloud-webhooks"; + +@customElement("cloud-account") +export class CloudAccount extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public isWide = false; + + @property({ type: Boolean }) public narrow = false; + + @property({ attribute: false }) public cloudStatus!: CloudStatusLoggedIn; + + @state() private _subscription?: SubscriptionInfo; + + @state() private _rtlDirection: "rtl" | "ltr" = "rtl"; + + protected render() { + return html` + +
+ + Home Assistant Cloud +
+

+ ${this.hass.localize( + "ui.panel.config.cloud.account.thank_you_note" + )} +

+
+ + + + + + +
+ + + ${this.hass.localize( + "ui.panel.config.cloud.account.manage_account" + )} + + + ${this.hass.localize( + "ui.panel.config.cloud.account.sign_out" + )} +
+
+
+ + + ${this.hass.localize( + "ui.panel.config.cloud.account.integrations" + )} +
+

+ ${this.hass.localize( + "ui.panel.config.cloud.account.integrations_introduction" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.cloud.account.integrations_introduction2" + )} + + ${this.hass.localize( + "ui.panel.config.cloud.account.integrations_link_all_features" + )}. +

+
+ + + + + + + + + + +
+
+
+ `; + } + + firstUpdated() { + this._fetchSubscriptionInfo(); + } + + protected updated(changedProps: PropertyValues) { + if (changedProps.has("hass")) { + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + if (!oldHass || oldHass.locale !== this.hass.locale) { + this._rtlDirection = computeRTLDirection(this.hass); + } + } + } + + private async _fetchSubscriptionInfo() { + this._subscription = await fetchCloudSubscriptionInfo(this.hass); + if ( + this._subscription.provider && + this.cloudStatus && + this.cloudStatus.cloud !== "connected" + ) { + fireEvent(this, "ha-refresh-cloud-status"); + } + } + + private async _handleLogout() { + await cloudLogout(this.hass); + fireEvent(this, "ha-refresh-cloud-status"); + } + + _computeRTLDirection(hass) { + return computeRTLDirection(hass); + } + + static get styles() { + return css` + [slot="introduction"] { + margin: -1em 0; + } + [slot="introduction"] a { + color: var(--primary-color); + } + .content { + padding-bottom: 24px; + } + .account-row { + display: flex; + padding: 0 16px; + } + .card-actions { + display: flex; + justify-content: space-between; + } + .card-actions a { + text-decoration: none; + } + mwc-button { + align-self: center; + } + .wrap { + white-space: normal; + } + .status { + text-transform: capitalize; + padding: 16px; + } + a { + color: var(--primary-color); + } + `; + } +} + +customElements.define("cloud-account", CloudAccount); + +declare global { + interface HTMLElementTagNameMap { + "cloud-account": CloudAccount; + } +} diff --git a/src/panels/config/cloud/forgot-password/cloud-forgot-password.js b/src/panels/config/cloud/forgot-password/cloud-forgot-password.js deleted file mode 100644 index 46b45c9022..0000000000 --- a/src/panels/config/cloud/forgot-password/cloud-forgot-password.js +++ /dev/null @@ -1,152 +0,0 @@ -import "@polymer/paper-input/paper-input"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../../components/buttons/ha-progress-button"; -import "../../../../components/ha-card"; -import "../../../../layouts/hass-subpage"; -import { EventsMixin } from "../../../../mixins/events-mixin"; -import LocalizeMixin from "../../../../mixins/localize-mixin"; -import "../../../../styles/polymer-ha-style"; - -/* - * @appliesMixin EventsMixin - * @appliesMixin LocalizeMixin - */ -class CloudForgotPassword extends LocalizeMixin(EventsMixin(PolymerElement)) { - static get template() { - return html` - - -
- -
-

- [[localize('ui.panel.config.cloud.forgot_password.instructions')]] -

-
[[_error]]
- -
-
- [[localize('ui.panel.config.cloud.forgot_password.send_reset_email')]] -
-
-
-
- `; - } - - static get properties() { - return { - hass: Object, - narrow: Boolean, - email: { - type: String, - notify: true, - observer: "_emailChanged", - }, - _requestInProgress: { - type: Boolean, - value: false, - }, - _error: { - type: String, - value: "", - }, - }; - } - - _emailChanged() { - this._error = ""; - this.$.email.invalid = false; - } - - _keyDown(ev) { - // validate on enter - if (ev.keyCode === 13) { - this._handleEmailPasswordReset(); - ev.preventDefault(); - } - } - - _handleEmailPasswordReset() { - if (!this.email || !this.email.includes("@")) { - this.$.email.invalid = true; - } - - if (this.$.email.invalid) return; - - this._requestInProgress = true; - - this.hass - .callApi("post", "cloud/forgot_password", { - email: this.email, - }) - .then( - () => { - this._requestInProgress = false; - this.fire("cloud-done", { - flashMessage: this.hass.localize( - "ui.panel.config.cloud.forgot_password.check_your_email" - ), - }); - }, - (err) => - this.setProperties({ - _requestInProgress: false, - _error: - err && err.body && err.body.message - ? err.body.message - : "Unknown error", - }) - ); - } -} - -customElements.define("cloud-forgot-password", CloudForgotPassword); diff --git a/src/panels/config/cloud/forgot-password/cloud-forgot-password.ts b/src/panels/config/cloud/forgot-password/cloud-forgot-password.ts new file mode 100644 index 0000000000..a6b34b9054 --- /dev/null +++ b/src/panels/config/cloud/forgot-password/cloud-forgot-password.ts @@ -0,0 +1,156 @@ +import "@material/mwc-textfield/mwc-textfield"; +import type { TextField } from "@material/mwc-textfield/mwc-textfield"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/buttons/ha-progress-button"; +import "../../../../components/ha-alert"; +import "../../../../components/ha-card"; +import { cloudForgotPassword } from "../../../../data/cloud"; +import "../../../../layouts/hass-subpage"; +import { haStyle } from "../../../../resources/styles"; +import { HomeAssistant } from "../../../../types"; + +@customElement("cloud-forgot-password") +export class CloudForgotPassword extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow = false; + + @property() public email?: string; + + @state() public _requestInProgress = false; + + @state() private _error?: string; + + @query("#email", true) private _emailField!: TextField; + + protected render(): TemplateResult { + return html` + +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.cloud.forgot_password.instructions" + )} +

+ ${this._error + ? html`${this._error}` + : ""} + +
+
+ + ${this.hass.localize( + "ui.panel.config.cloud.forgot_password.send_reset_email" + )} + +
+
+
+
+ `; + } + + private _keyDown(ev: KeyboardEvent) { + if (ev.key === "Enter") { + this._handleEmailPasswordReset(); + } + } + + private async _handleEmailPasswordReset() { + const emailField = this._emailField; + + const email = emailField.value; + + if (!emailField.reportValidity()) { + emailField.focus(); + return; + } + + this._requestInProgress = true; + + try { + await cloudForgotPassword(this.hass, email); + // @ts-ignore + fireEvent(this, "email-changed", { value: email }); + this._requestInProgress = false; + // @ts-ignore + fireEvent(this, "cloud-done", { + flashMessage: this.hass.localize( + "ui.panel.config.cloud.forgot_password.check_your_email" + ), + }); + } catch (err: any) { + this._requestInProgress = false; + this._error = + err && err.body && err.body.message + ? err.body.message + : "Unknown error"; + } + } + + static get styles() { + return [ + haStyle, + css` + .content { + padding-bottom: 24px; + } + ha-card { + max-width: 600px; + margin: 0 auto; + margin-top: 24px; + } + h1 { + margin: 0; + } + mwc-textfield { + width: 100%; + } + .card-actions { + display: flex; + justify-content: space-between; + align-items: center; + } + .card-actions a { + color: var(--primary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "cloud-forgot-password": CloudForgotPassword; + } +} diff --git a/src/panels/config/cloud/login/cloud-login.js b/src/panels/config/cloud/login/cloud-login.js deleted file mode 100644 index 937e0e6a4e..0000000000 --- a/src/panels/config/cloud/login/cloud-login.js +++ /dev/null @@ -1,336 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-ripple/paper-ripple"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { computeRTL } from "../../../../common/util/compute_rtl"; -import "../../../../components/buttons/ha-progress-button"; -import "../../../../components/ha-card"; -import "../../../../components/ha-icon"; -import "../../../../components/ha-icon-button"; -import "../../../../components/ha-icon-next"; -import "../../../../layouts/hass-subpage"; -import { EventsMixin } from "../../../../mixins/events-mixin"; -import LocalizeMixin from "../../../../mixins/localize-mixin"; -import NavigateMixin from "../../../../mixins/navigate-mixin"; -import "../../../../styles/polymer-ha-style"; -import "../../ha-config-section"; - -/* - * @appliesMixin NavigateMixin - * @appliesMixin EventsMixin - * @appliesMixin LocalizeMixin - */ -class CloudLogin extends LocalizeMixin( - NavigateMixin(EventsMixin(PolymerElement)) -) { - static get template() { - return html` - - -
- - Home Assistant Cloud -
-

[[localize('ui.panel.config.cloud.login.introduction')]]

-

- [[localize('ui.panel.config.cloud.login.introduction2')]] - - Nabu Casa, Inc[[localize('ui.panel.config.cloud.login.introduction2a')]] -

-

[[localize('ui.panel.config.cloud.login.introduction3')]]

-

- - [[localize('ui.panel.config.cloud.login.learn_more_link')]] - -

-
- - -
- [[flashMessage]] - - - - -
-
- - - -
- [[localize('ui.panel.config.cloud.login.sign_in')]] -
-
- - - - - [[localize('ui.panel.config.cloud.login.start_trial')]] -
- [[localize('ui.panel.config.cloud.login.trial_info')]] -
-
- -
-
-
-
-
- `; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - narrow: Boolean, - email: { - type: String, - notify: true, - }, - _password: { - type: String, - value: "", - }, - _requestInProgress: { - type: Boolean, - value: false, - }, - flashMessage: { - type: String, - notify: true, - }, - rtl: { - type: Boolean, - reflectToAttribute: true, - computed: "_computeRTL(hass)", - }, - _error: String, - }; - } - - static get observers() { - return ["_inputChanged(email, _password)"]; - } - - connectedCallback() { - super.connectedCallback(); - if (this.flashMessage) { - // Wait for DOM to be drawn - requestAnimationFrame(() => - requestAnimationFrame(() => this.$.flashRipple.simulatedRipple()) - ); - } - } - - _inputChanged() { - this.$.email.invalid = false; - this.$.password.invalid = false; - this._error = false; - } - - _keyDown(ev) { - // validate on enter - if (ev.keyCode === 13) { - this._handleLogin(); - ev.preventDefault(); - } - } - - _handleLogin() { - let invalid = false; - - if (!this.email || !this.email.includes("@")) { - this.$.email.invalid = true; - this.$.email.focus(); - invalid = true; - } - - if (this._password.length < 8) { - this.$.password.invalid = true; - - if (!invalid) { - invalid = true; - this.$.password.focus(); - } - } - - if (invalid) return; - - this._requestInProgress = true; - - this.hass - .callApi("post", "cloud/login", { - email: this.email, - password: this._password, - }) - .then( - () => { - this.fire("ha-refresh-cloud-status"); - this.setProperties({ - email: "", - _password: "", - }); - }, - (err) => { - // Do this before setProperties because changing it clears errors. - this._password = ""; - - const errCode = err && err.body && err.body.code; - if (errCode === "PasswordChangeRequired") { - alert( - "[[localize('ui.panel.config.cloud.login.alert_password_change_required')]]" - ); - this.navigate("/config/cloud/forgot-password"); - return; - } - - const props = { - _requestInProgress: false, - _error: - err && err.body && err.body.message - ? err.body.message - : "Unknown error", - }; - - if (errCode === "UserNotConfirmed") { - props._error = - "[[localize('ui.panel.config.cloud.login.alert_email_confirm_necessary')]]"; - } - - this.setProperties(props); - this.$.email.focus(); - } - ); - } - - _handleRegister() { - this.flashMessage = ""; - this.navigate("/config/cloud/register"); - } - - _handleForgotPassword() { - this.flashMessage = ""; - this.navigate("/config/cloud/forgot-password"); - } - - _dismissFlash() { - // give some time to let the ripple finish. - setTimeout(() => { - this.flashMessage = ""; - }, 200); - } - - _computeRTL(hass) { - return computeRTL(hass); - } -} - -customElements.define("cloud-login", CloudLogin); diff --git a/src/panels/config/cloud/login/cloud-login.ts b/src/panels/config/cloud/login/cloud-login.ts new file mode 100644 index 0000000000..c058985a60 --- /dev/null +++ b/src/panels/config/cloud/login/cloud-login.ts @@ -0,0 +1,310 @@ +import "@material/mwc-button"; +import "@material/mwc-textfield/mwc-textfield"; +import type { TextField } from "@material/mwc-textfield/mwc-textfield"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-item/paper-item-body"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { navigate } from "../../../../common/navigate"; +import "../../../../components/buttons/ha-progress-button"; +import "../../../../components/ha-alert"; +import "../../../../components/ha-card"; +import "../../../../components/ha-icon-next"; +import { cloudLogin } from "../../../../data/cloud"; +import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box"; +import "../../../../layouts/hass-subpage"; +import { haStyle } from "../../../../resources/styles"; +import "../../../../styles/polymer-ha-style"; +import { HomeAssistant } from "../../../../types"; +import "../../ha-config-section"; + +@customElement("cloud-login") +export class CloudLogin extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public isWide = false; + + @property({ type: Boolean }) public narrow = false; + + @property() public email?: string; + + @property() public flashMessage?: string; + + @state() private _password?: string; + + @state() private _requestInProgress = false; + + @state() private _error?: string; + + @query("#email", true) private _emailField!: TextField; + + @query("#password", true) private _passwordField!: TextField; + + protected render(): TemplateResult { + return html` + +
+ + Home Assistant Cloud +
+

+ ${this.hass.localize( + "ui.panel.config.cloud.login.introduction" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.cloud.login.introduction2" + )} + + Nabu Casa, Inc${this.hass.localize( + "ui.panel.config.cloud.login.introduction2a" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.cloud.login.introduction3" + )} +

+

+ + ${this.hass.localize( + "ui.panel.config.cloud.login.learn_more_link" + )} + +

+
+ + ${this.flashMessage + ? html` + ${this.flashMessage} + ` + : ""} + + + +
+ ${this.hass.localize( + "ui.panel.config.cloud.login.sign_in" + )} +
+
+ + + + + ${this.hass.localize( + "ui.panel.config.cloud.login.start_trial" + )} +
+ ${this.hass.localize( + "ui.panel.config.cloud.login.trial_info" + )} +
+
+ +
+
+
+
+
+ `; + } + + private _keyDown(ev: KeyboardEvent) { + if (ev.key === "Enter") { + this._handleLogin(); + } + } + + private async _handleLogin() { + const emailField = this._emailField; + const passwordField = this._passwordField; + + const email = emailField.value; + const password = passwordField.value; + + if (!emailField.reportValidity()) { + passwordField.reportValidity(); + emailField.focus(); + return; + } + + if (!passwordField.reportValidity()) { + passwordField.focus(); + return; + } + + this._requestInProgress = true; + + try { + await cloudLogin(this.hass, email, password); + fireEvent(this, "ha-refresh-cloud-status"); + this.email = ""; + this._password = ""; + } catch (err: any) { + const errCode = err && err.body && err.body.code; + if (errCode === "PasswordChangeRequired") { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.cloud.login.alert_password_change_required" + ), + }); + navigate("/config/cloud/forgot-password"); + return; + } + + this._password = ""; + this._requestInProgress = false; + + if (errCode === "UserNotConfirmed") { + this._error = this.hass.localize( + "ui.panel.config.cloud.login.alert_email_confirm_necessary" + ); + } else { + this._error = + err && err.body && err.body.message + ? err.body.message + : "Unknown error"; + } + + emailField.focus(); + } + } + + private _handleRegister() { + this._dismissFlash(); + // @ts-ignore + fireEvent(this, "email-changed", { value: this._emailField.value }); + navigate("/config/cloud/register"); + } + + private _handleForgotPassword() { + this._dismissFlash(); + // @ts-ignore + fireEvent(this, "email-changed", { value: this._emailField.value }); + navigate("/config/cloud/forgot-password"); + } + + private _dismissFlash() { + // @ts-ignore + fireEvent(this, "flash-message-changed", { value: "" }); + } + + static get styles() { + return [ + haStyle, + css` + .content { + padding-bottom: 24px; + } + [slot="introduction"] { + margin: -1em 0; + } + [slot="introduction"] a { + color: var(--primary-color); + } + paper-item { + cursor: pointer; + } + ha-card { + overflow: hidden; + } + ha-card .card-header { + margin-bottom: -8px; + } + h1 { + margin: 0; + } + .card-actions { + display: flex; + justify-content: space-between; + align-items: center; + } + .login-form { + display: flex; + flex-direction: column; + } + .pwd-forgot-link { + color: var(--secondary-text-color) !important; + text-align: right !important; + align-self: flex-end; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "cloud-login": CloudLogin; + } +} diff --git a/src/panels/config/cloud/register/cloud-register.js b/src/panels/config/cloud/register/cloud-register.js deleted file mode 100644 index b07a19d2f5..0000000000 --- a/src/panels/config/cloud/register/cloud-register.js +++ /dev/null @@ -1,229 +0,0 @@ -import "@polymer/paper-input/paper-input"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../../components/buttons/ha-progress-button"; -import "../../../../components/ha-card"; -import "../../../../layouts/hass-subpage"; -import { EventsMixin } from "../../../../mixins/events-mixin"; -import LocalizeMixin from "../../../../mixins/localize-mixin"; -import "../../../../styles/polymer-ha-style"; -import { documentationUrl } from "../../../../util/documentation-url"; -import "../../ha-config-section"; - -/* - * @appliesMixin EventsMixin - * @appliesMixin LocalizeMixin - */ -class CloudRegister extends LocalizeMixin(EventsMixin(PolymerElement)) { - static get template() { - return html` - - -
- - [[localize('ui.panel.config.cloud.register.headline')]] -
-

- [[localize('ui.panel.config.cloud.register.information')]] -

-

- [[localize('ui.panel.config.cloud.register.information2')]] -

-
    -
  • [[localize('ui.panel.config.cloud.register.feature_remote_control')]]
  • -
  • [[localize('ui.panel.config.cloud.register.feature_google_home')]]
  • -
  • [[localize('ui.panel.config.cloud.register.feature_amazon_alexa')]]
  • -
  • [[localize('ui.panel.config.cloud.register.feature_webhook_apps')]]
  • -
-

- [[localize('ui.panel.config.cloud.register.information3')]] Nabu Casa, Inc[[localize('ui.panel.config.cloud.register.information3a')]] -

- -

- [[localize('ui.panel.config.cloud.register.information4')]] -

-

-
- - -
-
-
[[_error]]
-
- - -
-
- [[localize('ui.panel.config.cloud.register.start_trial')]] - -
-
-
-
-
-`; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - narrow: Boolean, - email: { - type: String, - notify: true, - }, - - _requestInProgress: { - type: Boolean, - value: false, - }, - _password: { - type: String, - value: "", - }, - _error: { - type: String, - value: "", - }, - }; - } - - static get observers() { - return ["_inputChanged(email, _password)"]; - } - - _inputChanged() { - this._error = ""; - this.$.email.invalid = false; - this.$.password.invalid = false; - } - - _keyDown(ev) { - // validate on enter - if (ev.keyCode === 13) { - this._handleRegister(); - ev.preventDefault(); - } - } - - _computeDocumentationUrlTos(hass) { - return documentationUrl(hass, "/tos/"); - } - - _computeDocumentationUrlPrivacy(hass) { - return documentationUrl(hass, "/privacy/"); - } - - _handleRegister() { - let invalid = false; - - if (!this.email || !this.email.includes("@")) { - this.$.email.invalid = true; - this.$.email.focus(); - invalid = true; - } - - if (this._password.length < 8) { - this.$.password.invalid = true; - - if (!invalid) { - invalid = true; - this.$.password.focus(); - } - } - - if (invalid) return; - - this._requestInProgress = true; - - this.hass - .callApi("post", "cloud/register", { - email: this.email, - password: this._password, - }) - .then( - () => this._verificationEmailSent(), - (err) => { - // Do this before setProperties because changing it clears errors. - this._password = ""; - - this.setProperties({ - _requestInProgress: false, - _error: - err && err.body && err.body.message - ? err.body.message - : "Unknown error", - }); - } - ); - } - - _handleResendVerifyEmail() { - if (!this.email) { - this.$.email.invalid = true; - return; - } - - this.hass - .callApi("post", "cloud/resend_confirm", { - email: this.email, - }) - .then( - () => this._verificationEmailSent(), - (err) => - this.setProperties({ - _error: - err && err.body && err.body.message - ? err.body.message - : "Unknown error", - }) - ); - } - - _verificationEmailSent() { - this.setProperties({ - _requestInProgress: false, - _password: "", - }); - this.fire("cloud-done", { - flashMessage: this.hass.localize( - "ui.panel.config.cloud.register.account_created" - ), - }); - } -} - -customElements.define("cloud-register", CloudRegister); diff --git a/src/panels/config/cloud/register/cloud-register.ts b/src/panels/config/cloud/register/cloud-register.ts new file mode 100644 index 0000000000..10e539b05b --- /dev/null +++ b/src/panels/config/cloud/register/cloud-register.ts @@ -0,0 +1,293 @@ +import "@material/mwc-textfield/mwc-textfield"; +import type { TextField } from "@material/mwc-textfield/mwc-textfield"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/buttons/ha-progress-button"; +import "../../../../components/ha-alert"; +import "../../../../components/ha-card"; +import { cloudRegister, cloudResendVerification } from "../../../../data/cloud"; +import "../../../../layouts/hass-subpage"; +import { haStyle } from "../../../../resources/styles"; +import { HomeAssistant } from "../../../../types"; +import "../../ha-config-section"; + +@customElement("cloud-register") +export class CloudRegister extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public isWide = false; + + @property({ type: Boolean }) public narrow = false; + + @property() public email?: string; + + @state() private _requestInProgress = false; + + @state() private _password = ""; + + @state() private _error?: string; + + @query("#email", true) private _emailField!: TextField; + + @query("#password", true) private _passwordField!: TextField; + + protected render(): TemplateResult { + return html` + +
+ + ${this.hass.localize( + "ui.panel.config.cloud.register.headline" + )} +
+

+ ${this.hass.localize( + "ui.panel.config.cloud.register.information" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.cloud.register.information2" + )} +

+
    +
  • + ${this.hass.localize( + "ui.panel.config.cloud.register.feature_remote_control" + )} +
  • +
  • + ${this.hass.localize( + "ui.panel.config.cloud.register.feature_google_home" + )} +
  • +
  • + ${this.hass.localize( + "ui.panel.config.cloud.register.feature_amazon_alexa" + )} +
  • +
  • + ${this.hass.localize( + "ui.panel.config.cloud.register.feature_webhook_apps" + )} +
  • +
+

+ ${this.hass.localize( + "ui.panel.config.cloud.register.information3" + )} + Nabu Casa, Inc + ${this.hass.localize( + "ui.panel.config.cloud.register.information3a" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.cloud.register.information4" + )} +

+ +
+
+ ${this._error + ? html`${this._error}` + : ""} + + +
+
+ ${this.hass.localize( + "ui.panel.config.cloud.register.start_trial" + )} + +
+
+
+
+
+ `; + } + + private _keyDown(ev: KeyboardEvent) { + if (ev.key === "Enter") { + this._handleRegister(); + } + } + + private async _handleRegister() { + const emailField = this._emailField; + const passwordField = this._passwordField; + + const email = emailField.value; + const password = passwordField.value; + + if (!emailField.reportValidity()) { + passwordField.reportValidity(); + emailField.focus(); + return; + } + + if (!passwordField.reportValidity()) { + passwordField.focus(); + return; + } + + this._requestInProgress = true; + + try { + await cloudRegister(this.hass, email, password); + this._verificationEmailSent(email); + } catch (err: any) { + this._password = ""; + this._requestInProgress = false; + this._error = + err && err.body && err.body.message + ? err.body.message + : "Unknown error"; + } + } + + private async _handleResendVerifyEmail() { + const emailField = this._emailField; + + const email = emailField.value; + + if (!emailField.reportValidity()) { + emailField.focus(); + return; + } + + try { + await cloudResendVerification(this.hass, email); + this._verificationEmailSent(email); + } catch (err: any) { + this._error = + err && err.body && err.body.message + ? err.body.message + : "Unknown error"; + } + } + + private _verificationEmailSent(email: string) { + this._requestInProgress = false; + this._password = ""; + // @ts-ignore + fireEvent(this, "email-changed", { value: email }); + // @ts-ignore + fireEvent(this, "cloud-done", { + flashMessage: this.hass.localize( + "ui.panel.config.cloud.register.account_created" + ), + }); + } + + static get styles() { + return [ + haStyle, + css` + [slot="introduction"] { + margin: -1em 0; + } + [slot="introduction"] a { + color: var(--primary-color); + } + a { + color: var(--primary-color); + } + paper-item { + cursor: pointer; + } + h1 { + margin: 0; + } + .register-form { + display: flex; + flex-direction: column; + } + .card-actions { + display: flex; + justify-content: space-between; + align-items: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "cloud-register": CloudRegister; + } +} diff --git a/yarn.lock b/yarn.lock index fda56d395b..39f57277b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3343,7 +3343,7 @@ __metadata: languageName: node linkType: hard -"@polymer/paper-ripple@npm:^3.0.0-pre.26, @polymer/paper-ripple@npm:^3.0.2": +"@polymer/paper-ripple@npm:^3.0.0-pre.26": version: 3.0.2 resolution: "@polymer/paper-ripple@npm:3.0.2" dependencies: @@ -9038,7 +9038,6 @@ fsevents@^1.2.7: "@polymer/paper-input": ^3.2.1 "@polymer/paper-item": ^3.0.1 "@polymer/paper-listbox": ^3.0.1 - "@polymer/paper-ripple": ^3.0.2 "@polymer/paper-slider": ^3.0.1 "@polymer/paper-styles": ^3.0.1 "@polymer/paper-tabs": ^3.1.0