Skip to content

Commit

Permalink
Add cloud signup to voice flow (#22941)
Browse files Browse the repository at this point in the history
  • Loading branch information
bramkragten authored Nov 22, 2024
1 parent 3e02d95 commit f8fb5d7
Show file tree
Hide file tree
Showing 4 changed files with 590 additions and 154 deletions.
174 changes: 174 additions & 0 deletions src/dialogs/voice-assistant-setup/cloud/cloud-step-intro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { mdiEarth, mdiMicrophoneMessage, mdiOpenInNew } from "@mdi/js";
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button";
import "../../../components/ha-svg-icon";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { AssistantSetupStyles } from "../styles";

@customElement("cloud-step-intro")
export class CloudStepIntro extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

render() {
return html`<div class="content">
<img
src=${`/static/images/logo_nabu_casa${this.hass.themes?.darkMode ? "_dark" : ""}.png`}
alt="Nabu Casa logo"
/>
<h1>The power of Home Assistant Cloud</h1>
<div class="features">
<div class="feature speech">
<div class="logos">
<div class="round-icon">
<ha-svg-icon .path=${mdiMicrophoneMessage}></ha-svg-icon>
</div>
</div>
<h2>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.cloud.features.speech.title"
)}
<span class="no-wrap"></span>
</h2>
<p>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.cloud.features.speech.text"
)}
</p>
</div>
<div class="feature access">
<div class="logos">
<div class="round-icon">
<ha-svg-icon .path=${mdiEarth}></ha-svg-icon>
</div>
</div>
<h2>
Remote access
<span class="no-wrap"></span>
</h2>
<p>
Secure remote access to your system while supporting the
development of Home Assistant.
</p>
</div>
<div class="feature">
<div class="logos">
<img
alt="Google Assistant"
src=${brandsUrl({
domain: "google_assistant",
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<img
alt="Amazon Alexa"
src=${brandsUrl({
domain: "alexa",
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
</div>
<h2>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.cloud.features.assistants.title"
)}
</h2>
<p>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.cloud.features.assistants.text"
)}
</p>
</div>
</div>
</div>
<div class="footer side-by-side">
<a
href="https://www.nabucasa.com"
target="_blank"
rel="noreferrer noopenner"
>
<ha-button>
<ha-svg-icon .path=${mdiOpenInNew} slot="icon"></ha-svg-icon>
nabucasa.com
</ha-button>
</a>
<ha-button unelevated @click=${this._signUp}
>Try 1 month for free</ha-button
>
</div>`;
}

private _signUp() {
fireEvent(this, "cloud-step", { step: "SIGNUP" });
}

static styles = [
AssistantSetupStyles,
css`
:host {
display: flex;
}
.features {
display: flex;
flex-direction: column;
grid-gap: 16px;
padding: 16px;
}
.feature {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin-bottom: 16px;
}
.feature .logos {
margin-bottom: 16px;
}
.feature .logos > * {
width: 40px;
height: 40px;
margin: 0 4px;
}
.round-icon {
border-radius: 50%;
color: #6e41ab;
background-color: #e8dcf7;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.access .round-icon {
color: #00aef8;
background-color: #cceffe;
}
.feature h2 {
font-weight: 500;
font-size: 16px;
line-height: 24px;
margin-top: 0;
margin-bottom: 8px;
}
.feature p {
font-weight: 400;
font-size: 14px;
line-height: 20px;
margin: 0;
}
`,
];
}

declare global {
interface HTMLElementTagNameMap {
"cloud-step-intro": CloudStepIntro;
}
}
167 changes: 167 additions & 0 deletions src/dialogs/voice-assistant-setup/cloud/cloud-step-signin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { LitElement, css, html } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-password-field";
import type { HaPasswordField } from "../../../components/ha-password-field";
import "../../../components/ha-svg-icon";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import { cloudLogin } from "../../../data/cloud";
import type { HomeAssistant } from "../../../types";
import { showAlertDialog } from "../../generic/show-dialog-box";
import { AssistantSetupStyles } from "../styles";

@customElement("cloud-step-signin")
export class CloudStepSignin extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@state() private _requestInProgress = false;

@state() private _error?: string;

@query("#email", true) private _emailField!: HaTextField;

@query("#password", true) private _passwordField!: HaPasswordField;

render() {
return html`<div class="content">
<img
src=${`/static/images/logo_nabu_casa${this.hass.themes?.darkMode ? "_dark" : ""}.png`}
alt="Nabu Casa logo"
/>
<h1>Sign in</h1>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-textfield
autofocus
id="email"
name="email"
.label=${this.hass.localize(
"ui.panel.config.cloud.register.email_address"
)}
.disabled=${this._requestInProgress}
type="email"
autocomplete="email"
required
@keydown=${this._keyDown}
validationMessage=${this.hass.localize(
"ui.panel.config.cloud.register.email_error_msg"
)}
></ha-textfield>
<ha-password-field
id="password"
name="password"
.label=${this.hass.localize(
"ui.panel.config.cloud.register.password"
)}
.disabled=${this._requestInProgress}
autocomplete="new-password"
minlength="8"
required
@keydown=${this._keyDown}
validationMessage=${this.hass.localize(
"ui.panel.config.cloud.register.password_error_msg"
)}
></ha-password-field>
</div>
<div class="footer">
<ha-button
unelevated
@click=${this._handleLogin}
.disabled=${this._requestInProgress}
>Sign in</ha-button
>
</div>`;
}

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;

const doLogin = async (username: string) => {
try {
await cloudLogin(this.hass, username, password);
} catch (err: any) {
const errCode = err && err.body && err.body.code;

if (errCode === "usernotfound" && username !== username.toLowerCase()) {
await doLogin(username.toLowerCase());
return;
}

if (errCode === "PasswordChangeRequired") {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.cloud.login.alert_password_change_required"
),
});
navigate("/config/cloud/forgot-password");
fireEvent(this, "closed");
return;
}

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();
}
};

await doLogin(email);
}

static styles = [
AssistantSetupStyles,
css`
:host {
display: block;
}
ha-textfield,
ha-password-field {
display: block;
}
`,
];
}

declare global {
interface HTMLElementTagNameMap {
"cloud-step-signin": CloudStepSignin;
}
}
Loading

0 comments on commit f8fb5d7

Please sign in to comment.