mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 21:37:21 +00:00
Convert cloud account config to Lit (#10350)
This commit is contained in:
parent
7c2e0aea92
commit
95c6adc739
@ -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",
|
||||
|
@ -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<CloudStatus>({ type: "cloud/status" });
|
||||
|
||||
|
@ -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`
|
||||
<style include="iron-flex ha-style">
|
||||
[slot="introduction"] {
|
||||
margin: -1em 0;
|
||||
}
|
||||
[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.content {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.account-row {
|
||||
display: flex;
|
||||
padding: 0 16px;
|
||||
}
|
||||
mwc-button {
|
||||
align-self: center;
|
||||
}
|
||||
.soon {
|
||||
font-style: italic;
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.wrap {
|
||||
white-space: normal;
|
||||
}
|
||||
.status {
|
||||
text-transform: capitalize;
|
||||
padding: 16px;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
<hass-subpage
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
header="Home Assistant Cloud"
|
||||
>
|
||||
<div class="content">
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.account.thank_you_note')]]
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.cloud.account.nabu_casa_account')]]"
|
||||
>
|
||||
<div class="account-row">
|
||||
<paper-item-body two-line="">
|
||||
[[cloudStatus.email]]
|
||||
<div secondary class="wrap">
|
||||
[[_formatSubscription(_subscription)]]
|
||||
</div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
|
||||
<div class="account-row">
|
||||
<paper-item-body
|
||||
>[[localize('ui.panel.config.cloud.account.connection_status')]]</paper-item-body
|
||||
>
|
||||
<div class="status">
|
||||
[[_computeConnectionStatus(cloudStatus.cloud)]]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<a
|
||||
href="https://account.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<mwc-button
|
||||
>[[localize('ui.panel.config.cloud.account.manage_account')]]</mwc-button
|
||||
>
|
||||
</a>
|
||||
<mwc-button style="float: right" on-click="handleLogout"
|
||||
>[[localize('ui.panel.config.cloud.account.sign_out')]]</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header"
|
||||
>[[localize('ui.panel.config.cloud.account.integrations')]]</span
|
||||
>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.account.integrations_introduction')]]
|
||||
</p>
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.account.integrations_introduction2')]]
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
[[localize('ui.panel.config.cloud.account.integrations_link_all_features')]]</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<cloud-remote-pref
|
||||
hass="[[hass]]"
|
||||
cloud-status="[[cloudStatus]]"
|
||||
dir="[[_rtlDirection]]"
|
||||
></cloud-remote-pref>
|
||||
|
||||
<cloud-tts-pref
|
||||
hass="[[hass]]"
|
||||
cloud-status="[[cloudStatus]]"
|
||||
dir="[[_rtlDirection]]"
|
||||
></cloud-tts-pref>
|
||||
|
||||
<cloud-alexa-pref
|
||||
hass="[[hass]]"
|
||||
cloud-status="[[cloudStatus]]"
|
||||
dir="[[_rtlDirection]]"
|
||||
></cloud-alexa-pref>
|
||||
|
||||
<cloud-google-pref
|
||||
hass="[[hass]]"
|
||||
cloud-status="[[cloudStatus]]"
|
||||
dir="[[_rtlDirection]]"
|
||||
></cloud-google-pref>
|
||||
|
||||
<cloud-webhooks
|
||||
hass="[[hass]]"
|
||||
cloud-status="[[cloudStatus]]"
|
||||
dir="[[_rtlDirection]]"
|
||||
></cloud-webhooks>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
268
src/panels/config/cloud/account/cloud-account.ts
Normal file
268
src/panels/config/cloud/account/cloud-account.ts
Normal file
@ -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`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
header="Home Assistant Cloud"
|
||||
>
|
||||
<div class="content">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.thank_you_note"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.nabu_casa_account"
|
||||
)}
|
||||
>
|
||||
<div class="account-row">
|
||||
<paper-item-body two-line>
|
||||
${this.cloudStatus.email}
|
||||
<div secondary class="wrap">
|
||||
${this._subscription
|
||||
? this._subscription.human_description.replace(
|
||||
"{periodEnd}",
|
||||
this._subscription.plan_renewal_date
|
||||
? formatDateTime(
|
||||
new Date(
|
||||
this._subscription.plan_renewal_date * 1000
|
||||
),
|
||||
this.hass.locale
|
||||
)
|
||||
: ""
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.fetching_subscription"
|
||||
)}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
|
||||
<div class="account-row">
|
||||
<paper-item-body>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.connection_status"
|
||||
)}
|
||||
</paper-item-body>
|
||||
<div class="status">
|
||||
${this.cloudStatus.cloud === "connected"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.cloud.account.connected"
|
||||
)
|
||||
: this.cloudStatus.cloud === "disconnected"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.cloud.account.not_connected"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.cloud.account.connecting"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<a
|
||||
href="https://account.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.manage_account"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this._handleLogout}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.sign_out"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.integrations"
|
||||
)}</span
|
||||
>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.integrations_introduction"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.integrations_introduction2"
|
||||
)}
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.integrations_link_all_features"
|
||||
)}</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<cloud-remote-pref
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-remote-pref>
|
||||
|
||||
<cloud-tts-pref
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-tts-pref>
|
||||
|
||||
<cloud-alexa-pref
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-alexa-pref>
|
||||
|
||||
<cloud-google-pref
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-google-pref>
|
||||
|
||||
<cloud-webhooks
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${this._rtlDirection}
|
||||
></cloud-webhooks>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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`
|
||||
<style include="iron-flex ha-style">
|
||||
.content {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
margin-top: 24px;
|
||||
}
|
||||
h1 {
|
||||
@apply --paper-font-headline;
|
||||
margin: 0;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.card-actions a {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<hass-subpage
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
header="[[localize('ui.panel.config.cloud.forgot_password.title')]]"
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.cloud.forgot_password.subtitle')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.forgot_password.instructions')]]
|
||||
</p>
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
<paper-input
|
||||
autofocus=""
|
||||
id="email"
|
||||
label="[[localize('ui.panel.config.cloud.forgot_password.email')]]"
|
||||
value="{{email}}"
|
||||
type="email"
|
||||
on-keydown="_keyDown"
|
||||
error-message="[[localize('ui.panel.config.cloud.forgot_password.email_error_msg')]]"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
on-click="_handleEmailPasswordReset"
|
||||
progress="[[_requestInProgress]]"
|
||||
>[[localize('ui.panel.config.cloud.forgot_password.send_reset_email')]]</ha-progress-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
156
src/panels/config/cloud/forgot-password/cloud-forgot-password.ts
Normal file
156
src/panels/config/cloud/forgot-password/cloud-forgot-password.ts
Normal file
@ -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`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.title"
|
||||
)}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.subtitle"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.instructions"
|
||||
)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<mwc-textfield
|
||||
autofocus
|
||||
id="email"
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.email"
|
||||
)}
|
||||
.value=${this.email}
|
||||
type="email"
|
||||
required
|
||||
@keydown=${this._keyDown}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.email_error_msg"
|
||||
)}
|
||||
></mwc-textfield>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._handleEmailPasswordReset}
|
||||
.progress=${this._requestInProgress}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.forgot_password.send_reset_email"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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`
|
||||
<style include="iron-flex ha-style">
|
||||
.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 {
|
||||
@apply --paper-font-headline;
|
||||
margin: 0;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.flash-msg {
|
||||
padding-right: 44px;
|
||||
}
|
||||
.flash-msg ha-icon-button {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host([rtl]) .flash-msg ha-icon-button {
|
||||
right: auto;
|
||||
left: 8px;
|
||||
}
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pwd-forgot-link {
|
||||
color: var(--secondary-text-color) !important;
|
||||
text-align: right !important;
|
||||
align-self: flex-end;
|
||||
}
|
||||
</style>
|
||||
<hass-subpage
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
header="Home Assistant Cloud"
|
||||
>
|
||||
<div class="content">
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<div slot="introduction">
|
||||
<p>[[localize('ui.panel.config.cloud.login.introduction')]]</p>
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.login.introduction2')]]
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Nabu Casa, Inc</a
|
||||
>[[localize('ui.panel.config.cloud.login.introduction2a')]]
|
||||
</p>
|
||||
<p>[[localize('ui.panel.config.cloud.login.introduction3')]]</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
[[localize('ui.panel.config.cloud.login.learn_more_link')]]
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-card hidden$="[[!flashMessage]]">
|
||||
<div class="card-content flash-msg">
|
||||
[[flashMessage]]
|
||||
<ha-icon-button
|
||||
label="[[localize('ui.panel.config.cloud.login.dismiss')]]"
|
||||
on-click="_dismissFlash"
|
||||
>
|
||||
<ha-icon icon="hass:close"></ha-icon>
|
||||
</ha-icon-button>
|
||||
<paper-ripple id="flashRipple" noink=""></paper-ripple>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.cloud.login.sign_in')]]"
|
||||
>
|
||||
<div class="card-content login-form">
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
<paper-input
|
||||
label="[[localize('ui.panel.config.cloud.login.email')]]"
|
||||
id="email"
|
||||
type="email"
|
||||
value="{{email}}"
|
||||
on-keydown="_keyDown"
|
||||
error-message="[[localize('ui.panel.config.cloud.login.email_error_msg')]]"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
id="password"
|
||||
label="[[localize('ui.panel.config.cloud.login.password')]]"
|
||||
value="{{_password}}"
|
||||
type="password"
|
||||
on-keydown="_keyDown"
|
||||
error-message="[[localize('ui.panel.config.cloud.login.password_error_msg')]]"
|
||||
></paper-input>
|
||||
<button
|
||||
class="link pwd-forgot-link"
|
||||
hidden="[[_requestInProgress]]"
|
||||
on-click="_handleForgotPassword"
|
||||
>
|
||||
[[localize('ui.panel.config.cloud.login.forgot_password')]]
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
on-click="_handleLogin"
|
||||
progress="[[_requestInProgress]]"
|
||||
>[[localize('ui.panel.config.cloud.login.sign_in')]]</ha-progress-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-card>
|
||||
<paper-item on-click="_handleRegister">
|
||||
<paper-item-body two-line="">
|
||||
[[localize('ui.panel.config.cloud.login.start_trial')]]
|
||||
<div secondary="">
|
||||
[[localize('ui.panel.config.cloud.login.trial_info')]]
|
||||
</div>
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
310
src/panels/config/cloud/login/cloud-login.ts
Normal file
310
src/panels/config/cloud/login/cloud-login.ts
Normal file
@ -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`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
header="Home Assistant Cloud"
|
||||
>
|
||||
<div class="content">
|
||||
<ha-config-section isWide=${this.isWide}>
|
||||
<span slot="header">Home Assistant Cloud</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction2"
|
||||
)}
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Nabu Casa, Inc</a
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction2a"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.introduction3"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.nabucasa.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.learn_more_link"
|
||||
)}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
${this.flashMessage
|
||||
? html`<ha-alert
|
||||
dismissable
|
||||
@alert-dismissed-clicked=${this._dismissFlash}
|
||||
>
|
||||
${this.flashMessage}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.sign_in"
|
||||
)}
|
||||
>
|
||||
<div class="card-content login-form">
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<mwc-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.email"
|
||||
)}
|
||||
id="email"
|
||||
type="email"
|
||||
required
|
||||
.value=${this.email}
|
||||
@keydown=${this._keyDown}
|
||||
.disabled=${this._requestInProgress}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.email_error_msg"
|
||||
)}
|
||||
></mwc-textfield>
|
||||
<mwc-textfield
|
||||
id="password"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.password"
|
||||
)}
|
||||
.value=${this._password || ""}
|
||||
type="password"
|
||||
required
|
||||
minlength="8"
|
||||
@keydown=${this._keyDown}
|
||||
.disabled=${this._requestInProgress}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.password_error_msg"
|
||||
)}
|
||||
></mwc-textfield>
|
||||
<button
|
||||
class="link pwd-forgot-link"
|
||||
.disabled=${this._requestInProgress}
|
||||
@click=${this._handleForgotPassword}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.forgot_password"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._handleLogin}
|
||||
.progress=${this._requestInProgress}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.sign_in"
|
||||
)}</ha-progress-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-card>
|
||||
<paper-item @click=${this._handleRegister}>
|
||||
<paper-item-body two-line>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.start_trial"
|
||||
)}
|
||||
<div secondary>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.login.trial_info"
|
||||
)}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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`
|
||||
<style include="iron-flex ha-style">
|
||||
[slot=introduction] {
|
||||
margin: -1em 0;
|
||||
}
|
||||
[slot=introduction] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
h1 {
|
||||
@apply --paper-font-headline;
|
||||
margin: 0;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<hass-subpage hass="[[hass]]" narrow="[[narrow]]" header="[[localize('ui.panel.config.cloud.register.title')]]">
|
||||
<div class="content">
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">[[localize('ui.panel.config.cloud.register.headline')]]</span>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.register.information')]]
|
||||
</p>
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.register.information2')]]
|
||||
</p>
|
||||
<ul>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_remote_control')]]</li>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_google_home')]]</li>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_amazon_alexa')]]</li>
|
||||
<li>[[localize('ui.panel.config.cloud.register.feature_webhook_apps')]]</li>
|
||||
</ul>
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.register.information3')]] <a href='https://www.nabucasa.com' target='_blank'>Nabu Casa, Inc</a>[[localize('ui.panel.config.cloud.register.information3a')]]
|
||||
</p>
|
||||
|
||||
<p>
|
||||
[[localize('ui.panel.config.cloud.register.information4')]]
|
||||
</p><ul>
|
||||
<li><a href="[[_computeDocumentationUrlTos(hass)]]" target="_blank" rel="noreferrer">[[localize('ui.panel.config.cloud.register.link_terms_conditions')]]</a></li>
|
||||
<li><a href="[[_computeDocumentationUrlPrivacy(hass)]]" target="_blank" rel="noreferrer">[[localize('ui.panel.config.cloud.register.link_privacy_policy')]]</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ha-card header="[[localize('ui.panel.config.cloud.register.create_account')]]">
|
||||
<div class="card-content">
|
||||
<div class="header">
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
</div>
|
||||
<paper-input autofocus="" id="email" label="[[localize('ui.panel.config.cloud.register.email_address')]]" type="email" value="{{email}}" on-keydown="_keyDown" error-message="[[localize('ui.panel.config.cloud.register.email_error_msg')]]"></paper-input>
|
||||
<paper-input id="password" label="Password" value="{{_password}}" type="password" on-keydown="_keyDown" error-message="[[localize('ui.panel.config.cloud.register.password_error_msg')]]"></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button on-click="_handleRegister" progress="[[_requestInProgress]]">[[localize('ui.panel.config.cloud.register.start_trial')]]</ha-progress-button>
|
||||
<button class="link" hidden="[[_requestInProgress]]" on-click="_handleResendVerifyEmail">[[localize('ui.panel.config.cloud.register.resend_confirmation_email')]]</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
293
src/panels/config/cloud/register/cloud-register.ts
Normal file
293
src/panels/config/cloud/register/cloud-register.ts
Normal file
@ -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`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.cloud.register.title")}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.headline"
|
||||
)}</span
|
||||
>
|
||||
<div slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.information"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.information2"
|
||||
)}
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.feature_remote_control"
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.feature_google_home"
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.feature_amazon_alexa"
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.feature_webhook_apps"
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.information3"
|
||||
)}
|
||||
<a href="https://www.nabucasa.com" target="_blank"
|
||||
>Nabu Casa, Inc</a
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.information3a"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.information4"
|
||||
)}
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.nabucasa.com/tos/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.link_terms_conditions"
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.nabucasa.com/privacy_policy/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.link_privacy_policy"
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.create_account"
|
||||
)}
|
||||
><div class="card-content register-form">
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<mwc-textfield
|
||||
autofocus
|
||||
id="email"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.email_address"
|
||||
)}
|
||||
type="email"
|
||||
required
|
||||
.value=${this.email}
|
||||
@keydown=${this._keyDown}
|
||||
validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.email_error_msg"
|
||||
)}
|
||||
></mwc-textfield>
|
||||
<mwc-textfield
|
||||
id="password"
|
||||
label="Password"
|
||||
.value=${this._password}
|
||||
type="password"
|
||||
minlength="8"
|
||||
required
|
||||
@keydown=${this._keyDown}
|
||||
validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.password_error_msg"
|
||||
)}
|
||||
></mwc-textfield>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._handleRegister}
|
||||
.progress=${this._requestInProgress}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.start_trial"
|
||||
)}</ha-progress-button
|
||||
>
|
||||
<button
|
||||
class="link"
|
||||
.disabled=${this._requestInProgress}
|
||||
@click=${this._handleResendVerifyEmail}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.cloud.register.resend_confirm_email"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user