diff --git a/gulp/tasks/gen-onboarding-html.js b/gulp/tasks/gen-onboarding-html.js
new file mode 100644
index 0000000000..3ad18f00ce
--- /dev/null
+++ b/gulp/tasks/gen-onboarding-html.js
@@ -0,0 +1,43 @@
+const gulp = require('gulp');
+const path = require('path');
+const replace = require('gulp-batch-replace');
+const rename = require('gulp-rename');
+const md5 = require('../common/md5');
+const url = require('url');
+
+const config = require('../config');
+const minifyStream = require('../common/transform').minifyStream;
+
+const buildReplaces = {
+ '/frontend_latest/onboarding.js': 'onboarding.js',
+};
+
+const es5Extra = "";
+
+async function buildOnboarding(es6) {
+ const targetPath = es6 ? config.output : config.output_es5;
+ const targetUrl = es6 ? '/frontend_latest/' : '/frontend_es5/';
+ const frontendPath = es6 ? 'frontend_latest' : 'frontend_es5';
+ const toReplace = [
+ ['', es6 ? '' : es5Extra],
+ ['/home-assistant-polymer/hass_frontend/onboarding.js', `/${frontendPath}/onboarding.js`],
+ ];
+
+ for (const [replaceSearch, filename] of Object.entries(buildReplaces)) {
+ const parsed = path.parse(filename);
+ const hash = md5(path.resolve(targetPath, filename));
+ toReplace.push([
+ replaceSearch,
+ url.resolve(targetUrl, `${parsed.name}-${hash}${parsed.ext}`)]);
+ }
+
+ const stream = gulp.src(path.resolve(config.polymer_dir, 'src/onboarding.html'))
+ .pipe(replace(toReplace));
+
+ return minifyStream(stream, /* es6= */ es6)
+ .pipe(rename('onboarding.html'))
+ .pipe(gulp.dest(es6 ? config.output : config.output_es5));
+}
+
+gulp.task('gen-onboarding-html-es5', () => buildOnboarding(/* es6= */ false));
+gulp.task('gen-onboarding-html', () => buildOnboarding(/* es6= */ true));
diff --git a/script/build_frontend b/script/build_frontend
index b7126bd40f..30e05e017e 100755
--- a/script/build_frontend
+++ b/script/build_frontend
@@ -29,5 +29,5 @@ echo "CREATED_AT = `date +%s`" >> $OUTPUT_DIR_ES5/__init__.py
# Generate index.htmls with the MD5 hash of the builds
./node_modules/.bin/gulp \
gen-index-html gen-index-html-es5 \
- gen-authorize-html gen-authorize-html-es5
-
+ gen-authorize-html gen-authorize-html-es5 \
+ gen-onboarding-html gen-onboarding-html-es5
diff --git a/script/develop b/script/develop
index d164828f85..e800ee7830 100755
--- a/script/develop
+++ b/script/develop
@@ -15,5 +15,6 @@ cp -r public/__init__.py $OUTPUT_DIR_ES5/
./node_modules/.bin/gulp build-translations gen-icons
cp src/authorize.html $OUTPUT_DIR
+cp src/onboarding.html $OUTPUT_DIR
./node_modules/.bin/webpack --watch --progress
diff --git a/src/authorize.html b/src/authorize.html
index 106b41c311..4873cb10e0 100644
--- a/src/authorize.html
+++ b/src/authorize.html
@@ -4,6 +4,17 @@
Home Assistant
+
Loading
diff --git a/src/entrypoints/app.js b/src/entrypoints/app.js
index 1c8ebdf801..2ac312c825 100644
--- a/src/entrypoints/app.js
+++ b/src/entrypoints/app.js
@@ -24,7 +24,7 @@ import '../resources/ha-style.js';
import '../util/ha-pref-storage.js';
import { getActiveTranslation, getTranslation } from '../util/hass-translation.js';
import '../util/legacy-support';
-import '../util/roboto.js';
+import '../resources/roboto.js';
import hassCallApi from '../util/hass-call-api.js';
import makeDialogManager from '../dialogs/dialog-manager.js';
import registerServiceWorker from '../util/register-service-worker.js';
diff --git a/src/entrypoints/authorize.js b/src/entrypoints/authorize.js
index b62c78ccd1..4f2c00dec9 100644
--- a/src/entrypoints/authorize.js
+++ b/src/entrypoints/authorize.js
@@ -5,6 +5,7 @@ import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import '../components/ha-iconset-svg.js';
+import '../resources/roboto.js';
import '../auth/ha-auth-flow.js';
import '../auth/ha-pick-auth-provider.js';
diff --git a/src/entrypoints/onboarding.js b/src/entrypoints/onboarding.js
new file mode 100644
index 0000000000..945fbe2e0c
--- /dev/null
+++ b/src/entrypoints/onboarding.js
@@ -0,0 +1,3 @@
+import '../components/ha-iconset-svg.js';
+import '../resources/roboto.js';
+import '../onboarding/ha-onboarding.js';
diff --git a/src/onboarding.html b/src/onboarding.html
new file mode 100644
index 0000000000..3ffa72caf6
--- /dev/null
+++ b/src/onboarding.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Home Assistant
+
+
+
+
+ Loading
+
+
+
+
+
diff --git a/src/onboarding/ha-onboarding.js b/src/onboarding/ha-onboarding.js
new file mode 100644
index 0000000000..58fe8c11af
--- /dev/null
+++ b/src/onboarding/ha-onboarding.js
@@ -0,0 +1,125 @@
+import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
+import '@polymer/polymer/lib/elements/dom-if.js';
+import '@polymer/polymer/lib/elements/dom-repeat.js';
+import '@polymer/paper-input/paper-input.js';
+import '@polymer/paper-button/paper-button.js';
+import { html } from '@polymer/polymer/lib/utils/html-tag.js';
+import { PolymerElement } from '@polymer/polymer/polymer-element.js';
+import hassCallApi from '../util/hass-call-api.js';
+
+const callApi = (method, path, data) => hassCallApi('', {}, method, path, data);
+
+class HaOnboarding extends PolymerElement {
+ static get template() {
+ return html`
+
+
+
+

+
+
Create your owner user account.
+
It is not possible yet to change your password. Coming soon!
+
+
+ [[_error]]
+
+
+
+
+
+
+
+
+
+
+ Submit
+
+
+
+`;
+ }
+
+ static get properties() {
+ return {
+ _name: String,
+ _username: String,
+ _password: String,
+ _loading: {
+ type: Boolean,
+ value: false,
+ }
+ };
+ }
+
+ async ready() {
+ super.ready();
+ this.addEventListener('keypress', (ev) => {
+ if (ev.keyCode === 13) {
+ this._submitForm();
+ }
+ });
+ const steps = await callApi('get', 'onboarding');
+ if (steps.every(step => step.done)) {
+ // Onboarding is done!
+ document.location = '/';
+ }
+ }
+
+ _maybePopulateUsername() {
+ if (!this._username) {
+ this._username = this._name.toLowerCase().replace(/ /g, '');
+ }
+ }
+
+ async _submitForm() {
+ if (!this._name || !this._username || !this._password) return;
+
+ try {
+ await callApi('post', 'onboarding/users', {
+ name: this._name,
+ username: this._username,
+ password: this._password,
+ });
+
+ document.location = '/';
+ } catch (err) {
+ // eslint-disable-next-line
+ console.error(err);
+ this.setProperties({
+ _loading: false,
+ _error: err.message,
+ });
+ }
+ }
+}
+customElements.define('ha-onboarding', HaOnboarding);
diff --git a/src/util/roboto.js b/src/resources/roboto.js
similarity index 100%
rename from src/util/roboto.js
rename to src/resources/roboto.js
diff --git a/webpack.config.js b/webpack.config.js
index 0ffec03254..920fcd0880 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -20,6 +20,7 @@ function createConfig(isProdBuild, latestBuild) {
const entry = {
app: './src/entrypoints/app.js',
authorize: './src/entrypoints/authorize.js',
+ onboarding: './src/entrypoints/onboarding.js',
core: './src/entrypoints/core.js',
compatibility: './src/entrypoints/compatibility.js',
'custom-panel': './src/entrypoints/custom-panel.js',