Skip to content

Commit f8fb5d7

Browse files
authored
Add cloud signup to voice flow (#22941)
1 parent 3e02d95 commit f8fb5d7

File tree

4 files changed

+590
-154
lines changed

4 files changed

+590
-154
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { mdiEarth, mdiMicrophoneMessage, mdiOpenInNew } from "@mdi/js";
2+
import { LitElement, css, html } from "lit";
3+
import { customElement, property } from "lit/decorators";
4+
import { fireEvent } from "../../../common/dom/fire_event";
5+
import "../../../components/ha-button";
6+
import "../../../components/ha-svg-icon";
7+
import type { HomeAssistant } from "../../../types";
8+
import { brandsUrl } from "../../../util/brands-url";
9+
import { AssistantSetupStyles } from "../styles";
10+
11+
@customElement("cloud-step-intro")
12+
export class CloudStepIntro extends LitElement {
13+
@property({ attribute: false }) public hass!: HomeAssistant;
14+
15+
render() {
16+
return html`<div class="content">
17+
<img
18+
src=${`/static/images/logo_nabu_casa${this.hass.themes?.darkMode ? "_dark" : ""}.png`}
19+
alt="Nabu Casa logo"
20+
/>
21+
<h1>The power of Home Assistant Cloud</h1>
22+
<div class="features">
23+
<div class="feature speech">
24+
<div class="logos">
25+
<div class="round-icon">
26+
<ha-svg-icon .path=${mdiMicrophoneMessage}></ha-svg-icon>
27+
</div>
28+
</div>
29+
<h2>
30+
${this.hass.localize(
31+
"ui.panel.config.voice_assistants.assistants.cloud.features.speech.title"
32+
)}
33+
<span class="no-wrap"></span>
34+
</h2>
35+
<p>
36+
${this.hass.localize(
37+
"ui.panel.config.voice_assistants.assistants.cloud.features.speech.text"
38+
)}
39+
</p>
40+
</div>
41+
<div class="feature access">
42+
<div class="logos">
43+
<div class="round-icon">
44+
<ha-svg-icon .path=${mdiEarth}></ha-svg-icon>
45+
</div>
46+
</div>
47+
<h2>
48+
Remote access
49+
<span class="no-wrap"></span>
50+
</h2>
51+
<p>
52+
Secure remote access to your system while supporting the
53+
development of Home Assistant.
54+
</p>
55+
</div>
56+
<div class="feature">
57+
<div class="logos">
58+
<img
59+
alt="Google Assistant"
60+
src=${brandsUrl({
61+
domain: "google_assistant",
62+
type: "icon",
63+
darkOptimized: this.hass.themes?.darkMode,
64+
})}
65+
crossorigin="anonymous"
66+
referrerpolicy="no-referrer"
67+
/>
68+
<img
69+
alt="Amazon Alexa"
70+
src=${brandsUrl({
71+
domain: "alexa",
72+
type: "icon",
73+
darkOptimized: this.hass.themes?.darkMode,
74+
})}
75+
crossorigin="anonymous"
76+
referrerpolicy="no-referrer"
77+
/>
78+
</div>
79+
<h2>
80+
${this.hass.localize(
81+
"ui.panel.config.voice_assistants.assistants.cloud.features.assistants.title"
82+
)}
83+
</h2>
84+
<p>
85+
${this.hass.localize(
86+
"ui.panel.config.voice_assistants.assistants.cloud.features.assistants.text"
87+
)}
88+
</p>
89+
</div>
90+
</div>
91+
</div>
92+
<div class="footer side-by-side">
93+
<a
94+
href="https://www.nabucasa.com"
95+
target="_blank"
96+
rel="noreferrer noopenner"
97+
>
98+
<ha-button>
99+
<ha-svg-icon .path=${mdiOpenInNew} slot="icon"></ha-svg-icon>
100+
nabucasa.com
101+
</ha-button>
102+
</a>
103+
<ha-button unelevated @click=${this._signUp}
104+
>Try 1 month for free</ha-button
105+
>
106+
</div>`;
107+
}
108+
109+
private _signUp() {
110+
fireEvent(this, "cloud-step", { step: "SIGNUP" });
111+
}
112+
113+
static styles = [
114+
AssistantSetupStyles,
115+
css`
116+
:host {
117+
display: flex;
118+
}
119+
.features {
120+
display: flex;
121+
flex-direction: column;
122+
grid-gap: 16px;
123+
padding: 16px;
124+
}
125+
.feature {
126+
display: flex;
127+
flex-direction: column;
128+
align-items: center;
129+
text-align: center;
130+
margin-bottom: 16px;
131+
}
132+
.feature .logos {
133+
margin-bottom: 16px;
134+
}
135+
.feature .logos > * {
136+
width: 40px;
137+
height: 40px;
138+
margin: 0 4px;
139+
}
140+
.round-icon {
141+
border-radius: 50%;
142+
color: #6e41ab;
143+
background-color: #e8dcf7;
144+
display: flex;
145+
align-items: center;
146+
justify-content: center;
147+
font-size: 24px;
148+
}
149+
.access .round-icon {
150+
color: #00aef8;
151+
background-color: #cceffe;
152+
}
153+
.feature h2 {
154+
font-weight: 500;
155+
font-size: 16px;
156+
line-height: 24px;
157+
margin-top: 0;
158+
margin-bottom: 8px;
159+
}
160+
.feature p {
161+
font-weight: 400;
162+
font-size: 14px;
163+
line-height: 20px;
164+
margin: 0;
165+
}
166+
`,
167+
];
168+
}
169+
170+
declare global {
171+
interface HTMLElementTagNameMap {
172+
"cloud-step-intro": CloudStepIntro;
173+
}
174+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import { LitElement, css, html } from "lit";
2+
import { customElement, property, query, state } from "lit/decorators";
3+
import { fireEvent } from "../../../common/dom/fire_event";
4+
import { navigate } from "../../../common/navigate";
5+
import "../../../components/ha-alert";
6+
import "../../../components/ha-button";
7+
import "../../../components/ha-password-field";
8+
import type { HaPasswordField } from "../../../components/ha-password-field";
9+
import "../../../components/ha-svg-icon";
10+
import "../../../components/ha-textfield";
11+
import type { HaTextField } from "../../../components/ha-textfield";
12+
import { cloudLogin } from "../../../data/cloud";
13+
import type { HomeAssistant } from "../../../types";
14+
import { showAlertDialog } from "../../generic/show-dialog-box";
15+
import { AssistantSetupStyles } from "../styles";
16+
17+
@customElement("cloud-step-signin")
18+
export class CloudStepSignin extends LitElement {
19+
@property({ attribute: false }) public hass!: HomeAssistant;
20+
21+
@state() private _requestInProgress = false;
22+
23+
@state() private _error?: string;
24+
25+
@query("#email", true) private _emailField!: HaTextField;
26+
27+
@query("#password", true) private _passwordField!: HaPasswordField;
28+
29+
render() {
30+
return html`<div class="content">
31+
<img
32+
src=${`/static/images/logo_nabu_casa${this.hass.themes?.darkMode ? "_dark" : ""}.png`}
33+
alt="Nabu Casa logo"
34+
/>
35+
<h1>Sign in</h1>
36+
${this._error
37+
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
38+
: ""}
39+
<ha-textfield
40+
autofocus
41+
id="email"
42+
name="email"
43+
.label=${this.hass.localize(
44+
"ui.panel.config.cloud.register.email_address"
45+
)}
46+
.disabled=${this._requestInProgress}
47+
type="email"
48+
autocomplete="email"
49+
required
50+
@keydown=${this._keyDown}
51+
validationMessage=${this.hass.localize(
52+
"ui.panel.config.cloud.register.email_error_msg"
53+
)}
54+
></ha-textfield>
55+
<ha-password-field
56+
id="password"
57+
name="password"
58+
.label=${this.hass.localize(
59+
"ui.panel.config.cloud.register.password"
60+
)}
61+
.disabled=${this._requestInProgress}
62+
autocomplete="new-password"
63+
minlength="8"
64+
required
65+
@keydown=${this._keyDown}
66+
validationMessage=${this.hass.localize(
67+
"ui.panel.config.cloud.register.password_error_msg"
68+
)}
69+
></ha-password-field>
70+
</div>
71+
<div class="footer">
72+
<ha-button
73+
unelevated
74+
@click=${this._handleLogin}
75+
.disabled=${this._requestInProgress}
76+
>Sign in</ha-button
77+
>
78+
</div>`;
79+
}
80+
81+
private _keyDown(ev: KeyboardEvent) {
82+
if (ev.key === "Enter") {
83+
this._handleLogin();
84+
}
85+
}
86+
87+
private async _handleLogin() {
88+
const emailField = this._emailField;
89+
const passwordField = this._passwordField;
90+
91+
const email = emailField.value;
92+
const password = passwordField.value;
93+
94+
if (!emailField.reportValidity()) {
95+
passwordField.reportValidity();
96+
emailField.focus();
97+
return;
98+
}
99+
100+
if (!passwordField.reportValidity()) {
101+
passwordField.focus();
102+
return;
103+
}
104+
105+
this._requestInProgress = true;
106+
107+
const doLogin = async (username: string) => {
108+
try {
109+
await cloudLogin(this.hass, username, password);
110+
} catch (err: any) {
111+
const errCode = err && err.body && err.body.code;
112+
113+
if (errCode === "usernotfound" && username !== username.toLowerCase()) {
114+
await doLogin(username.toLowerCase());
115+
return;
116+
}
117+
118+
if (errCode === "PasswordChangeRequired") {
119+
showAlertDialog(this, {
120+
title: this.hass.localize(
121+
"ui.panel.config.cloud.login.alert_password_change_required"
122+
),
123+
});
124+
navigate("/config/cloud/forgot-password");
125+
fireEvent(this, "closed");
126+
return;
127+
}
128+
129+
this._requestInProgress = false;
130+
131+
if (errCode === "UserNotConfirmed") {
132+
this._error = this.hass.localize(
133+
"ui.panel.config.cloud.login.alert_email_confirm_necessary"
134+
);
135+
} else {
136+
this._error =
137+
err && err.body && err.body.message
138+
? err.body.message
139+
: "Unknown error";
140+
}
141+
142+
emailField.focus();
143+
}
144+
};
145+
146+
await doLogin(email);
147+
}
148+
149+
static styles = [
150+
AssistantSetupStyles,
151+
css`
152+
:host {
153+
display: block;
154+
}
155+
ha-textfield,
156+
ha-password-field {
157+
display: block;
158+
}
159+
`,
160+
];
161+
}
162+
163+
declare global {
164+
interface HTMLElementTagNameMap {
165+
"cloud-step-signin": CloudStepSignin;
166+
}
167+
}

0 commit comments

Comments
 (0)