diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6ab6fa2dfc1..b2d486670e4 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -158,11 +158,6 @@ jobs:
run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000']
timeout-minutes: 30
- - name: Wizard QUnit (Legacy)
- if: matrix.build_type == 'frontend-legacy' && matrix.target == 'core'
- run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/wizard/qunit']
- timeout-minutes: 10
-
- name: Plugin QUnit (Legacy)
if: matrix.build_type == 'frontend-legacy' && matrix.target == 'plugins'
run: QUNIT_EMBER_CLI=0 bin/rake plugin:qunit['*','1200000']
diff --git a/app/assets/javascripts/discourse-common/addon/resolver.js b/app/assets/javascripts/discourse-common/addon/resolver.js
index c7914aa9121..c0b8162866a 100644
--- a/app/assets/javascripts/discourse-common/addon/resolver.js
+++ b/app/assets/javascripts/discourse-common/addon/resolver.js
@@ -93,6 +93,7 @@ export function buildResolver(baseName) {
if (split.length > 1) {
const appBase = `${baseName}/${split[0]}s/`;
const adminBase = "admin/" + split[0] + "s/";
+ const wizardBase = "wizard/" + split[0] + "s/";
// Allow render 'admin/templates/xyz' too
split[1] = split[1].replace(".templates", "").replace("/templates", "");
@@ -101,7 +102,8 @@ export function buildResolver(baseName) {
let dashed = dasherize(split[1].replace(/\./g, "/"));
if (
requirejs.entries[appBase + dashed] ||
- requirejs.entries[adminBase + dashed]
+ requirejs.entries[adminBase + dashed] ||
+ requirejs.entries[wizardBase + dashed]
) {
return split[0] + ":" + dashed;
}
@@ -110,7 +112,8 @@ export function buildResolver(baseName) {
dashed = dasherize(split[1].replace(/\./g, "-"));
if (
requirejs.entries[appBase + dashed] ||
- requirejs.entries[adminBase + dashed]
+ requirejs.entries[adminBase + dashed] ||
+ requirejs.entries[wizardBase + dashed]
) {
return split[0] + ":" + dashed;
}
@@ -253,6 +256,7 @@ export function buildResolver(baseName) {
templates[decamelized.replace(/\_/, "/")] ||
templates[`${baseName}/templates/${withoutType}`] ||
this.findAdminTemplate(parsedName) ||
+ this.findWizardTemplate(parsedName) ||
this.findUnderscoredTemplate(parsedName)
);
},
@@ -296,5 +300,37 @@ export function buildResolver(baseName) {
);
}
},
+
+ findWizardTemplate(parsedName) {
+ let decamelized = decamelize(parsedName.fullNameWithoutType);
+ if (decamelized.startsWith("components")) {
+ let comPath = `wizard/templates/${decamelized}`;
+ const compTemplate =
+ Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath];
+ if (compTemplate) {
+ return compTemplate;
+ }
+ }
+
+ if (decamelized === "javascripts/wizard") {
+ return Ember.TEMPLATES["wizard/templates/wizard"];
+ }
+
+ if (
+ decamelized.startsWith("wizard") ||
+ decamelized.startsWith("javascripts/wizard")
+ ) {
+ decamelized = decamelized.replace(/^wizard\_/, "wizard/templates/");
+ decamelized = decamelized.replace(/^wizard\./, "wizard/templates/");
+ decamelized = decamelized.replace(/\./g, "_");
+
+ const dashed = decamelized.replace(/_/g, "-");
+ return (
+ Ember.TEMPLATES[decamelized] ||
+ Ember.TEMPLATES[dashed] ||
+ Ember.TEMPLATES[dashed.replace("wizard-", "wizard/")]
+ );
+ }
+ },
});
}
diff --git a/app/assets/javascripts/discourse/app/components/global-notice.js b/app/assets/javascripts/discourse/app/components/global-notice.js
index 7432143273e..b2097becb6a 100644
--- a/app/assets/javascripts/discourse/app/components/global-notice.js
+++ b/app/assets/javascripts/discourse/app/components/global-notice.js
@@ -50,6 +50,8 @@ const Notice = EmberObject.extend({
});
export default Component.extend({
+ tagName: "",
+ router: service(),
logsNoticeService: service("logsNotice"),
logNotice: null,
@@ -70,6 +72,10 @@ export default Component.extend({
);
},
+ get visible() {
+ return !this.router.currentRouteName.startsWith("wizard.");
+ },
+
@discourseComputed(
"site.isReadOnly",
"site.wizard_required",
diff --git a/app/assets/javascripts/discourse/app/lib/url.js b/app/assets/javascripts/discourse/app/lib/url.js
index f9bc2565ca2..6c092a4f810 100644
--- a/app/assets/javascripts/discourse/app/lib/url.js
+++ b/app/assets/javascripts/discourse/app/lib/url.js
@@ -24,7 +24,6 @@ const SERVER_SIDE_ONLY = [
/^\/raw\//,
/^\/posts\/\d+\/raw/,
/^\/raw\/\d+/,
- /^\/wizard/,
/\.rss$/,
/\.json$/,
/^\/admin\/upgrade$/,
diff --git a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs
index 08ff36603fc..efb640d1be8 100644
--- a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs
@@ -1,19 +1,27 @@
-{{#each notices as |notice|}}
-
-
- {{#if notice.options.html}}
- {{html-safe notice.options.html}}
- {{/if}}
-
{{html-safe notice.text}}
+
+ {{#if this.visible}}
+ {{#each notices as |notice|}}
+
+
+ {{#if notice.options.html}}
+ {{html-safe notice.options.html}}
+ {{/if}}
- {{#if notice.options.dismissable}}
- {{d-button
- class="btn-flat close"
- icon="times"
- action=(action "dismissNotice")
- actionParam=notice
- }}
- {{/if}}
-
-
-{{/each}}
+
{{html-safe notice.text}}
+
+ {{#if notice.options.dismissable}}
+ {{d-button
+ class="btn-flat close"
+ icon="times"
+ action=(action "dismissNotice")
+ actionParam=notice
+ }}
+ {{/if}}
+
+
+ {{/each}}
+ {{/if}}
+
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js
index 60f707624d4..d9232652ab8 100644
--- a/app/assets/javascripts/discourse/ember-cli-build.js
+++ b/app/assets/javascripts/discourse/ember-cli-build.js
@@ -128,6 +128,9 @@ module.exports = function (defaults) {
concat(mergeTrees([app.options.adminTree]), {
outputFile: `assets/admin.js`,
}),
+ concat(mergeTrees([app.options.wizardTree]), {
+ outputFile: `assets/wizard.js`,
+ }),
prettyTextEngine(vendorJs, "discourse-markdown"),
concat("public/assets/scripts", {
outputFile: `assets/start-discourse.js`,
diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js
index 73f3a6e90dd..a1fcc954366 100644
--- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js
+++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js
@@ -85,10 +85,16 @@ function head(buffer, bootstrap, headers, baseURL) {
});
if (bootstrap.preloaded.currentUser) {
- let staff = JSON.parse(bootstrap.preloaded.currentUser).staff;
+ const user = JSON.parse(bootstrap.preloaded.currentUser);
+ let { admin, staff } = user;
+
if (staff) {
buffer.push(``);
}
+
+ if (admin) {
+ buffer.push(``);
+ }
}
bootstrap.plugin_js.forEach((src) =>
diff --git a/app/assets/javascripts/discourse/lib/translation-plugin.js b/app/assets/javascripts/discourse/lib/translation-plugin.js
index d1e6543b335..3f23e8116ae 100644
--- a/app/assets/javascripts/discourse/lib/translation-plugin.js
+++ b/app/assets/javascripts/discourse/lib/translation-plugin.js
@@ -59,6 +59,7 @@ class TranslationPlugin extends Plugin {
let extras = {
en: {
admin: parsed.en.admin_js.admin,
+ wizard: parsed.en.wizard_js.wizard,
},
};
@@ -70,7 +71,7 @@ class TranslationPlugin extends Plugin {
this.replaceMF(formats, parsed);
this.replaceMF(formats, extras);
- formats = Object.keys(formats).map((k) => `"${k}": ${formats[k]}`);
+ formats = Object.entries(formats).map(([k, v]) => `"${k}": ${v}`);
let contents = `
I18n.locale = 'en';
diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json
index 7c064732097..ac38f94aae3 100644
--- a/app/assets/javascripts/discourse/package.json
+++ b/app/assets/javascripts/discourse/package.json
@@ -23,7 +23,6 @@
"@ember/test-helpers": "^2.2.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
- "tippy.js": "^6.3.7",
"@popperjs/core": "2.10.2",
"@uppy/aws-s3": "^2.0.8",
"@uppy/aws-s3-multipart": "^2.2.1",
@@ -72,8 +71,10 @@
"sass": "^1.32.8",
"select-kit": "^1.0.0",
"sinon": "^13.0.1",
+ "tippy.js": "^6.3.7",
"virtual-dom": "^2.1.1",
- "webpack": "^5.67.0"
+ "webpack": "^5.67.0",
+ "wizard": "^1.0.0"
},
"engines": {
"node": "12.* || 14.* || >= 16",
diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js
index a456dff9817..7dab11a480b 100644
--- a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js
+++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js
@@ -6,11 +6,12 @@
// TODO: Remove this and have resolver find the templates
const prefix = "discourse/templates/";
const adminPrefix = "admin/templates/";
+ const wizardPrefix = "wizard/templates/";
let len = prefix.length;
Object.keys(requirejs.entries).forEach(function (key) {
- if (key.indexOf(prefix) === 0) {
+ if (key.startsWith(prefix)) {
Ember.TEMPLATES[key.slice(len)] = require(key).default;
- } else if (key.indexOf(adminPrefix) === 0) {
+ } else if (key.startsWith(adminPrefix) || key.startsWith(wizardPrefix)) {
Ember.TEMPLATES[key] = require(key).default;
}
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js
new file mode 100644
index 00000000000..8bc467145ee
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js
@@ -0,0 +1,66 @@
+import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
+import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers";
+import { test } from "qunit";
+
+acceptance("Wizard", function (needs) {
+ needs.user();
+
+ test("Wizard starts", async function (assert) {
+ await visit("/wizard");
+ assert.ok(exists(".wizard-column-contents"));
+ assert.strictEqual(currentRouteName(), "wizard.step");
+ });
+
+ test("Going back and forth in steps", async function (assert) {
+ await visit("/wizard/steps/hello-world");
+ assert.ok(exists(".wizard-step"));
+ assert.ok(
+ exists(".wizard-step-hello-world"),
+ "it adds a class for the step id"
+ );
+ assert.ok(!exists(".wizard-btn.finish"), "cannot finish on first step");
+ assert.ok(exists(".wizard-progress"));
+ assert.ok(exists(".wizard-step-title"));
+ assert.ok(exists(".wizard-step-description"));
+ assert.ok(
+ !exists(".invalid .field-full-name"),
+ "don't show it as invalid until the user does something"
+ );
+ assert.ok(exists(".wizard-field .field-description"));
+ assert.ok(!exists(".wizard-btn.back"));
+ assert.ok(!exists(".wizard-field .field-error-description"));
+
+ // invalid data
+ await click(".wizard-btn.next");
+ assert.ok(exists(".invalid .field-full-name"));
+
+ // server validation fail
+ await fillIn("input.field-full-name", "Server Fail");
+ await click(".wizard-btn.next");
+ assert.ok(exists(".invalid .field-full-name"));
+ assert.ok(exists(".wizard-field .field-error-description"));
+
+ // server validation ok
+ await fillIn("input.field-full-name", "Evil Trout");
+ await click(".wizard-btn.next");
+ assert.ok(!exists(".wizard-field .field-error-description"));
+ assert.ok(!exists(".wizard-step-description"));
+ assert.ok(
+ exists(".wizard-btn.finish"),
+ "shows finish on an intermediate step"
+ );
+
+ await click(".wizard-btn.next");
+ assert.ok(exists(".select-kit.field-snack"), "went to the next step");
+ assert.ok(exists(".preview-area"), "renders the component field");
+ assert.ok(exists(".wizard-btn.done"), "last step shows a done button");
+ assert.ok(exists(".action-link.back"), "shows the back button");
+ assert.ok(!exists(".wizard-step-title"));
+ assert.ok(!exists(".wizard-btn.finish"), "cannot finish on last step");
+
+ await click(".action-link.back");
+ assert.ok(exists(".wizard-step-title"));
+ assert.ok(exists(".wizard-btn.next"));
+ assert.ok(!exists(".wizard-prev"));
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/index.html b/app/assets/javascripts/discourse/tests/index.html
index 98c7318aefd..cc64b777149 100644
--- a/app/assets/javascripts/discourse/tests/index.html
+++ b/app/assets/javascripts/discourse/tests/index.html
@@ -36,7 +36,6 @@
width: 1000px;
height: 1000px;
}
-
@@ -51,6 +50,7 @@
+
{{content-for "test-plugin-js"}}
diff --git a/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js
new file mode 100644
index 00000000000..39f54e07a63
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js
@@ -0,0 +1,100 @@
+import {
+ count,
+ discourseModule,
+ exists,
+} from "discourse/tests/helpers/qunit-helpers";
+import componentTest, {
+ setupRenderingTest,
+} from "discourse/tests/helpers/component-test";
+import { click, fillIn } from "@ember/test-helpers";
+import hbs from "htmlbars-inline-precompile";
+
+discourseModule(
+ "Integration | Component | Wizard | invite-list",
+ function (hooks) {
+ setupRenderingTest(hooks);
+
+ componentTest("can add users", {
+ template: hbs`{{invite-list field=field}}`,
+
+ beforeEach() {
+ this.set("field", {});
+ },
+
+ async test(assert) {
+ assert.ok(
+ !exists(".users-list .invite-list-user"),
+ "no users at first"
+ );
+ assert.ok(!exists(".new-user .invalid"), "not invalid at first");
+
+ const firstVal = JSON.parse(this.field.value);
+ assert.strictEqual(firstVal.length, 0, "empty JSON at first");
+
+ assert.ok(
+ this.field.warning,
+ "it has a warning since no users were added"
+ );
+
+ await click(".add-user");
+ assert.ok(
+ !exists(".users-list .invite-list-user"),
+ "doesn't add a blank user"
+ );
+ assert.strictEqual(count(".new-user .invalid"), 1);
+
+ await fillIn(".invite-email", "eviltrout@example.com");
+ await click(".add-user");
+
+ assert.strictEqual(
+ count(".users-list .invite-list-user"),
+ 1,
+ "adds the user"
+ );
+ assert.ok(!exists(".new-user .invalid"));
+
+ const val = JSON.parse(this.field.value);
+ assert.strictEqual(val.length, 1);
+ assert.strictEqual(
+ val[0].email,
+ "eviltrout@example.com",
+ "adds the email to the JSON"
+ );
+ assert.ok(val[0].role.length, "adds the role to the JSON");
+ assert.ok(
+ !this.get("field.warning"),
+ "no warning once the user is added"
+ );
+
+ await fillIn(".invite-email", "eviltrout@example.com");
+ await click(".add-user");
+
+ assert.strictEqual(
+ count(".users-list .invite-list-user"),
+ 1,
+ "can't add the same user twice"
+ );
+ assert.strictEqual(count(".new-user .invalid"), 1);
+
+ await fillIn(".invite-email", "not-an-email");
+ await click(".add-user");
+
+ assert.strictEqual(
+ count(".users-list .invite-list-user"),
+ 1,
+ "won't add an invalid email"
+ );
+ assert.strictEqual(count(".new-user .invalid"), 1);
+
+ await click(
+ ".invite-list .invite-list-user:nth-of-type(1) .remove-user"
+ );
+ assert.ok(
+ !exists(".users-list .invite-list-user"),
+ 0,
+ "removed the user"
+ );
+ },
+ });
+ }
+);
diff --git a/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js b/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js
new file mode 100644
index 00000000000..41b59bc72e1
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js
@@ -0,0 +1,35 @@
+import { module, test } from "qunit";
+import WizardField from "wizard/models/wizard-field";
+
+module("Unit | Model | Wizard | wizard-field", function () {
+ test("basic state", function (assert) {
+ const w = WizardField.create({ type: "text" });
+ assert.ok(w.unchecked);
+ assert.ok(!w.valid);
+ assert.ok(!w.invalid);
+ });
+
+ test("text - required - validation", function (assert) {
+ const w = WizardField.create({ type: "text", required: true });
+ assert.ok(w.unchecked);
+
+ w.check();
+ assert.ok(!w.unchecked);
+ assert.ok(!w.valid);
+ assert.ok(w.invalid);
+
+ w.set("value", "a value");
+ w.check();
+ assert.ok(!w.unchecked);
+ assert.ok(w.valid);
+ assert.ok(!w.invalid);
+ });
+
+ test("text - optional - validation", function (assert) {
+ const f = WizardField.create({ type: "text" });
+ assert.ok(f.unchecked);
+
+ f.check();
+ assert.ok(f.valid);
+ });
+});
diff --git a/app/assets/javascripts/package.json b/app/assets/javascripts/package.json
index d2b43f0a782..4c2ad27ab8f 100644
--- a/app/assets/javascripts/package.json
+++ b/app/assets/javascripts/package.json
@@ -8,6 +8,7 @@
"discourse-widget-hbs",
"pretty-text",
"select-kit",
- "truth-helpers"
+ "truth-helpers",
+ "wizard"
]
}
diff --git a/app/assets/javascripts/select-kit/addon/components/icon-picker.js b/app/assets/javascripts/select-kit/addon/components/icon-picker.js
index 6d506ab64dc..ab6553acbdf 100644
--- a/app/assets/javascripts/select-kit/addon/components/icon-picker.js
+++ b/app/assets/javascripts/select-kit/addon/components/icon-picker.js
@@ -7,7 +7,7 @@ import MultiSelectComponent from "select-kit/components/multi-select";
import { computed } from "@ember/object";
import { isDevelopment } from "discourse-common/config/environment";
import { makeArray } from "discourse-common/lib/helpers";
-import { ajax } from "select-kit/lib/ajax-helper";
+import { ajax } from "discourse/lib/ajax";
export default MultiSelectComponent.extend({
pluginApiIdentifiers: ["icon-picker"],
diff --git a/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js b/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js
deleted file mode 100644
index 4cd5d97ece6..00000000000
--- a/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js
+++ /dev/null
@@ -1,8 +0,0 @@
-let ajax;
-if (window.Discourse) {
- ajax = requirejs("discourse/lib/ajax").ajax;
-} else {
- ajax = requirejs("wizard/lib/ajax").ajax;
-}
-
-export { ajax };
diff --git a/app/assets/javascripts/select-kit/addon/mixins/tags.js b/app/assets/javascripts/select-kit/addon/mixins/tags.js
index fe4c34f50b2..caa1eaf3942 100644
--- a/app/assets/javascripts/select-kit/addon/mixins/tags.js
+++ b/app/assets/javascripts/select-kit/addon/mixins/tags.js
@@ -1,6 +1,6 @@
import I18n from "I18n";
import Mixin from "@ember/object/mixin";
-import { ajax } from "select-kit/lib/ajax-helper";
+import { ajax } from "discourse/lib/ajax";
import getURL from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils";
import { makeArray } from "discourse-common/lib/helpers";
diff --git a/app/assets/javascripts/wizard-application.js b/app/assets/javascripts/wizard-application.js
deleted file mode 100644
index 3c91a3ddefd..00000000000
--- a/app/assets/javascripts/wizard-application.js
+++ /dev/null
@@ -1,13 +0,0 @@
-//= require_tree ./truth-helpers/addon
-//= require_tree ./discourse-common/addon
-//= require_tree ./select-kit/addon
-//= require wizard/router
-//= require wizard/wizard
-//= require_tree ./wizard/templates
-//= require_tree ./wizard/components
-//= require_tree ./wizard/models
-//= require_tree ./wizard/routes
-//= require_tree ./wizard/controllers
-//= require_tree ./wizard/lib
-//= require_tree ./wizard/mixins
-//= require_tree ./wizard/initializers
diff --git a/app/assets/javascripts/wizard-shims.js b/app/assets/javascripts/wizard-shims.js
deleted file mode 100644
index b506393302f..00000000000
--- a/app/assets/javascripts/wizard-shims.js
+++ /dev/null
@@ -1,47 +0,0 @@
-define("@popperjs/core", ["exports"], function (__exports__) {
- __exports__.default = window.Popper;
- __exports__.createPopper = window.Popper.createPopper;
- __exports__.defaultModifiers = window.Popper.defaultModifiers;
- __exports__.popperGenerator = window.Popper.popperGenerator;
-});
-
-define("tippy.js", ["exports"], function (__exports__) {
- __exports__.default = window.tippy;
-});
-
-define("@uppy/core", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.Core;
- __exports__.BasePlugin = window.Uppy.Core.BasePlugin;
-});
-
-define("@uppy/aws-s3", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.AwsS3;
-});
-
-define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.AwsS3Multipart;
-});
-
-define("@uppy/xhr-upload", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.XHRUpload;
-});
-
-define("@uppy/drop-target", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.DropTarget;
-});
-
-define("@uppy/utils/lib/delay", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.Utils.delay;
-});
-
-define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.Utils.EventTracker;
-});
-
-define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) {
- __exports__.AbortController =
- window.Uppy.Utils.AbortControllerLib.AbortController;
- __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal;
- __exports__.createAbortError =
- window.Uppy.Utils.AbortControllerLib.createAbortError;
-});
diff --git a/app/assets/javascripts/wizard-start.js b/app/assets/javascripts/wizard-start.js
deleted file mode 100644
index e12c46d8f11..00000000000
--- a/app/assets/javascripts/wizard-start.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// discourse-skip-module
-(function () {
- const wizard = require("wizard/wizard").default.create();
- wizard.start();
-})();
diff --git a/app/assets/javascripts/wizard-vendor.js b/app/assets/javascripts/wizard-vendor.js
deleted file mode 100644
index 5d175a6bb59..00000000000
--- a/app/assets/javascripts/wizard-vendor.js
+++ /dev/null
@@ -1,10 +0,0 @@
-//= require ember_jquery
-//= require template_include.js
-//= require uppy.js
-//= require bootstrap-modal.js
-//= require bootbox.js
-//= require virtual-dom
-//= require virtual-dom-amd
-//= require popper.js
-//= require tippy.umd.js
-//= require wizard-shims
diff --git a/app/assets/javascripts/wizard.js b/app/assets/javascripts/wizard.js
new file mode 100644
index 00000000000..7f527381845
--- /dev/null
+++ b/app/assets/javascripts/wizard.js
@@ -0,0 +1,2 @@
+//= require discourse/app/lib/export-result
+//= require_tree ./wizard/addon
diff --git a/app/assets/javascripts/wizard/.npmrc b/app/assets/javascripts/wizard/.npmrc
new file mode 100644
index 00000000000..c42da845b44
--- /dev/null
+++ b/app/assets/javascripts/wizard/.npmrc
@@ -0,0 +1 @@
+engine-strict = true
diff --git a/app/assets/javascripts/wizard/components/homepage-preview.js b/app/assets/javascripts/wizard/addon/components/homepage-preview.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/homepage-preview.js
rename to app/assets/javascripts/wizard/addon/components/homepage-preview.js
diff --git a/app/assets/javascripts/wizard/components/image-preview-favicon.js b/app/assets/javascripts/wizard/addon/components/image-preview-favicon.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/image-preview-favicon.js
rename to app/assets/javascripts/wizard/addon/components/image-preview-favicon.js
diff --git a/app/assets/javascripts/wizard/components/image-preview-large-icon.js b/app/assets/javascripts/wizard/addon/components/image-preview-large-icon.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/image-preview-large-icon.js
rename to app/assets/javascripts/wizard/addon/components/image-preview-large-icon.js
diff --git a/app/assets/javascripts/wizard/components/image-preview-logo-small.js b/app/assets/javascripts/wizard/addon/components/image-preview-logo-small.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/image-preview-logo-small.js
rename to app/assets/javascripts/wizard/addon/components/image-preview-logo-small.js
diff --git a/app/assets/javascripts/wizard/components/image-preview-logo.js b/app/assets/javascripts/wizard/addon/components/image-preview-logo.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/image-preview-logo.js
rename to app/assets/javascripts/wizard/addon/components/image-preview-logo.js
diff --git a/app/assets/javascripts/wizard/components/invite-list-user.js b/app/assets/javascripts/wizard/addon/components/invite-list-user.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/invite-list-user.js
rename to app/assets/javascripts/wizard/addon/components/invite-list-user.js
diff --git a/app/assets/javascripts/wizard/components/invite-list.js b/app/assets/javascripts/wizard/addon/components/invite-list.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/invite-list.js
rename to app/assets/javascripts/wizard/addon/components/invite-list.js
diff --git a/app/assets/javascripts/wizard/components/staff-count.js b/app/assets/javascripts/wizard/addon/components/staff-count.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/staff-count.js
rename to app/assets/javascripts/wizard/addon/components/staff-count.js
diff --git a/app/assets/javascripts/wizard/components/styling-preview.js b/app/assets/javascripts/wizard/addon/components/styling-preview.js
similarity index 91%
rename from app/assets/javascripts/wizard/components/styling-preview.js
rename to app/assets/javascripts/wizard/addon/components/styling-preview.js
index 1a4deaa0750..f3bc3e941ad 100644
--- a/app/assets/javascripts/wizard/components/styling-preview.js
+++ b/app/assets/javascripts/wizard/addon/components/styling-preview.js
@@ -31,6 +31,18 @@ export default createPreviewComponent(659, 320, {
this.wizard.off("homepageStyleChanged", this.onHomepageStyleChange);
},
+ didInsertElement() {
+ this._super(...arguments);
+ this.element.addEventListener("mouseleave", this.handleMouseLeave);
+ this.element.addEventListener("mousemove", this.handleMouseMove);
+ },
+
+ willDestroyElement() {
+ this._super(...arguments);
+ this.element.removeEventListener("mouseleave", this.handleMouseLeave);
+ this.element.removeEventListener("mousemove", this.handleMouseMove);
+ },
+
mouseDown(e) {
const slider = this.element.querySelector(".previews");
this.setProperties({
@@ -40,7 +52,8 @@ export default createPreviewComponent(659, 320, {
});
},
- mouseLeave() {
+ @bind
+ handleMouseLeave() {
this.set("draggingActive", false);
},
@@ -48,7 +61,8 @@ export default createPreviewComponent(659, 320, {
this.set("draggingActive", false);
},
- mouseMove(e) {
+ @bind
+ handleMouseMove(e) {
if (!this.draggingActive) {
return;
}
diff --git a/app/assets/javascripts/wizard/components/wizard-canvas.js b/app/assets/javascripts/wizard/addon/components/wizard-canvas.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/wizard-canvas.js
rename to app/assets/javascripts/wizard/addon/components/wizard-canvas.js
diff --git a/app/assets/javascripts/wizard/components/wizard-field-checkboxes.js b/app/assets/javascripts/wizard/addon/components/wizard-field-checkboxes.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/wizard-field-checkboxes.js
rename to app/assets/javascripts/wizard/addon/components/wizard-field-checkboxes.js
diff --git a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js b/app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js
similarity index 83%
rename from app/assets/javascripts/wizard/components/wizard-field-dropdown.js
rename to app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js
index 99548fa4641..34b136cdf3d 100644
--- a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js
+++ b/app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js
@@ -3,8 +3,8 @@ import discourseComputed from "discourse-common/utils/decorators";
import { action, set } from "@ember/object";
export default Component.extend({
- init(...args) {
- this._super(...args);
+ init() {
+ this._super(...arguments);
if (this.field.id === "color_scheme") {
for (let choice of this.field.choices) {
@@ -17,10 +17,7 @@ export default Component.extend({
@discourseComputed("field.id")
componentName(id) {
- if (id === "color_scheme") {
- return "color-palettes";
- }
- return "combo-box";
+ return id === "color_scheme" ? "color-palettes" : "combo-box";
},
keyPress(e) {
diff --git a/app/assets/javascripts/wizard/components/wizard-field-image.js b/app/assets/javascripts/wizard/addon/components/wizard-field-image.js
similarity index 96%
rename from app/assets/javascripts/wizard/components/wizard-field-image.js
rename to app/assets/javascripts/wizard/addon/components/wizard-field-image.js
index c4ff8328b5c..365db6e836a 100644
--- a/app/assets/javascripts/wizard/components/wizard-field-image.js
+++ b/app/assets/javascripts/wizard/addon/components/wizard-field-image.js
@@ -4,7 +4,6 @@ import I18n from "I18n";
import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators";
import { getOwner } from "discourse-common/lib/get-owner";
-import { getToken } from "wizard/lib/ajax";
import getUrl from "discourse-common/lib/get-url";
import Uppy from "@uppy/core";
import DropTarget from "@uppy/drop-target";
@@ -37,7 +36,7 @@ export default Component.extend({
this._uppyInstance.use(XHRUpload, {
endpoint: getUrl("/uploads.json"),
headers: {
- "X-CSRF-Token": getToken(),
+ "X-CSRF-Token": this.session.csrfToken,
},
});
diff --git a/app/assets/javascripts/wizard/components/wizard-field-radio.js b/app/assets/javascripts/wizard/addon/components/wizard-field-radio.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/wizard-field-radio.js
rename to app/assets/javascripts/wizard/addon/components/wizard-field-radio.js
diff --git a/app/assets/javascripts/wizard/components/wizard-field-textarea.js b/app/assets/javascripts/wizard/addon/components/wizard-field-textarea.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/wizard-field-textarea.js
rename to app/assets/javascripts/wizard/addon/components/wizard-field-textarea.js
diff --git a/app/assets/javascripts/wizard/components/wizard-field.js b/app/assets/javascripts/wizard/addon/components/wizard-field.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/wizard-field.js
rename to app/assets/javascripts/wizard/addon/components/wizard-field.js
diff --git a/app/assets/javascripts/wizard/components/wizard-image-preview.js b/app/assets/javascripts/wizard/addon/components/wizard-image-preview.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/wizard-image-preview.js
rename to app/assets/javascripts/wizard/addon/components/wizard-image-preview.js
diff --git a/app/assets/javascripts/wizard/components/wizard-step-form.js b/app/assets/javascripts/wizard/addon/components/wizard-step-form.js
similarity index 100%
rename from app/assets/javascripts/wizard/components/wizard-step-form.js
rename to app/assets/javascripts/wizard/addon/components/wizard-step-form.js
diff --git a/app/assets/javascripts/wizard/components/wizard-step.js b/app/assets/javascripts/wizard/addon/components/wizard-step.js
similarity index 99%
rename from app/assets/javascripts/wizard/components/wizard-step.js
rename to app/assets/javascripts/wizard/addon/components/wizard-step.js
index b0957bbf9b6..2e6b95e2c49 100644
--- a/app/assets/javascripts/wizard/components/wizard-step.js
+++ b/app/assets/javascripts/wizard/addon/components/wizard-step.js
@@ -150,8 +150,10 @@ export default Component.extend({
if (result.warnings.length) {
const unwarned = result.warnings.filter((w) => !alreadyWarned[w]);
+
if (unwarned.length) {
unwarned.forEach((w) => (alreadyWarned[w] = true));
+
return window.bootbox.confirm(
unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"),
I18n.t("no_value"),
diff --git a/app/assets/javascripts/wizard/addon/controllers/wizard-step.js b/app/assets/javascripts/wizard/addon/controllers/wizard-step.js
new file mode 100644
index 00000000000..7bae0e0dcc7
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/controllers/wizard-step.js
@@ -0,0 +1,24 @@
+import getUrl from "discourse-common/lib/get-url";
+import Controller from "@ember/controller";
+import { action } from "@ember/object";
+
+export default Controller.extend({
+ wizard: null,
+ step: null,
+
+ @action
+ goNext(response) {
+ const next = this.get("step.next");
+
+ if (response?.refresh_required) {
+ document.location = getUrl(`/wizard/steps/${next}`);
+ } else if (response?.success) {
+ this.transitionToRoute("wizard.step", next);
+ }
+ },
+
+ @action
+ goBack() {
+ this.transitionToRoute("wizard.step", this.step.previous);
+ },
+});
diff --git a/app/assets/javascripts/wizard/lib/preview.js b/app/assets/javascripts/wizard/addon/lib/preview.js
similarity index 100%
rename from app/assets/javascripts/wizard/lib/preview.js
rename to app/assets/javascripts/wizard/addon/lib/preview.js
diff --git a/app/assets/javascripts/wizard/mixins/valid-state.js b/app/assets/javascripts/wizard/addon/mixins/valid-state.js
similarity index 100%
rename from app/assets/javascripts/wizard/mixins/valid-state.js
rename to app/assets/javascripts/wizard/addon/mixins/valid-state.js
diff --git a/app/assets/javascripts/wizard/models/step.js b/app/assets/javascripts/wizard/addon/models/step.js
similarity index 87%
rename from app/assets/javascripts/wizard/models/step.js
rename to app/assets/javascripts/wizard/addon/models/step.js
index ed67491b64b..3decd1dadb4 100644
--- a/app/assets/javascripts/wizard/models/step.js
+++ b/app/assets/javascripts/wizard/addon/models/step.js
@@ -1,13 +1,15 @@
import EmberObject from "@ember/object";
import ValidState from "wizard/mixins/valid-state";
-import { ajax } from "wizard/lib/ajax";
+import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators";
export default EmberObject.extend(ValidState, {
id: null,
@discourseComputed("index")
- displayIndex: (index) => index + 1,
+ displayIndex(index) {
+ return index + 1;
+ },
@discourseComputed("fields.[]")
fieldsById(fields) {
@@ -48,8 +50,8 @@ export default EmberObject.extend(ValidState, {
url: `/wizard/steps/${this.id}`,
type: "PUT",
data: { fields },
- }).catch((response) => {
- response.responseJSON.errors.forEach((err) =>
+ }).catch((error) => {
+ error.jqXHR.responseJSON.errors.forEach((err) =>
this.fieldError(err.field, err.description)
);
});
diff --git a/app/assets/javascripts/wizard/models/wizard-field.js b/app/assets/javascripts/wizard/addon/models/wizard-field.js
similarity index 100%
rename from app/assets/javascripts/wizard/models/wizard-field.js
rename to app/assets/javascripts/wizard/addon/models/wizard-field.js
diff --git a/app/assets/javascripts/wizard/models/wizard.js b/app/assets/javascripts/wizard/addon/models/wizard.js
similarity index 84%
rename from app/assets/javascripts/wizard/models/wizard.js
rename to app/assets/javascripts/wizard/addon/models/wizard.js
index 9f880c4e16a..fbdf8f86c08 100644
--- a/app/assets/javascripts/wizard/models/wizard.js
+++ b/app/assets/javascripts/wizard/addon/models/wizard.js
@@ -2,12 +2,11 @@ import EmberObject from "@ember/object";
import Evented from "@ember/object/evented";
import Step from "wizard/models/step";
import WizardField from "wizard/models/wizard-field";
-import { ajax } from "wizard/lib/ajax";
-import discourseComputed from "discourse-common/utils/decorators";
+import { ajax } from "discourse/lib/ajax";
+import { readOnly } from "@ember/object/computed";
const Wizard = EmberObject.extend(Evented, {
- @discourseComputed("steps.length")
- totalSteps: (length) => length,
+ totalSteps: readOnly("steps.length"),
getTitle() {
const titleStep = this.steps.findBy("id", "forum-title");
@@ -53,8 +52,7 @@ const Wizard = EmberObject.extend(Evented, {
});
export function findWizard() {
- return ajax({ url: "/wizard.json" }).then((response) => {
- const wizard = response.wizard;
+ return ajax({ url: "/wizard.json" }).then(({ wizard }) => {
wizard.steps = wizard.steps.map((step) => {
const stepObj = Step.create(step);
stepObj.fields = stepObj.fields.map((f) => WizardField.create(f));
diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-index.js b/app/assets/javascripts/wizard/addon/routes/wizard-index.js
new file mode 100644
index 00000000000..d6305f86193
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/routes/wizard-index.js
@@ -0,0 +1,8 @@
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default DiscourseRoute.extend({
+ beforeModel() {
+ const appModel = this.modelFor("wizard");
+ this.replaceWith("wizard.step", appModel.start);
+ },
+});
diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js b/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js
new file mode 100644
index 00000000000..62ed3323784
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js
@@ -0,0 +1,5 @@
+export default function () {
+ this.route("wizard", function () {
+ this.route("step", { path: "/steps/:step_id" });
+ });
+}
diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-step.js b/app/assets/javascripts/wizard/addon/routes/wizard-step.js
new file mode 100644
index 00000000000..76d78bc44d9
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/routes/wizard-step.js
@@ -0,0 +1,17 @@
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default DiscourseRoute.extend({
+ model(params) {
+ const allSteps = this.modelFor("wizard").steps;
+ const step = allSteps.findBy("id", params.step_id);
+
+ return step || allSteps[0];
+ },
+
+ setupController(controller, step) {
+ const wizard = this.modelFor("wizard");
+ this.controllerFor("wizard").set("currentStepId", step.id);
+
+ controller.setProperties({ step, wizard });
+ },
+});
diff --git a/app/assets/javascripts/wizard/addon/routes/wizard.js b/app/assets/javascripts/wizard/addon/routes/wizard.js
new file mode 100644
index 00000000000..73aa06c1420
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/routes/wizard.js
@@ -0,0 +1,21 @@
+import Route from "@ember/routing/route";
+import { findWizard } from "wizard/models/wizard";
+
+export default Route.extend({
+ model() {
+ return findWizard();
+ },
+
+ activate() {
+ document.body.classList.add("wizard");
+ this.controllerFor("application").setProperties({
+ showTop: false,
+ showFooter: false,
+ });
+ },
+
+ deactivate() {
+ document.body.classList.remove("wizard");
+ this.controllerFor("application").set("showTop", true);
+ },
+});
diff --git a/app/assets/javascripts/wizard/templates/components/invite-list-user.hbs b/app/assets/javascripts/wizard/addon/templates/components/invite-list-user.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/invite-list-user.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/invite-list-user.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/invite-list.hbs b/app/assets/javascripts/wizard/addon/templates/components/invite-list.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/invite-list.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/invite-list.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/staff-count.hbs b/app/assets/javascripts/wizard/addon/templates/components/staff-count.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/staff-count.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/staff-count.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/styling-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/styling-preview.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/styling-preview.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/styling-preview.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/theme-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/theme-preview.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/theme-preview.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/theme-preview.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-checkbox.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-field-checkbox.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-checkboxes.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkboxes.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-field-checkboxes.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkboxes.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-dropdown.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-dropdown.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-image.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-image.hbs
diff --git a/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs
new file mode 100644
index 00000000000..1e2bc2a0ba4
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs
@@ -0,0 +1,30 @@
+{{#each field.choices as |choice|}}
+
+
+ {{radio-button
+ selection=field.value
+ value=choice.id
+ name=choice.label
+ onChange=(action "changed")
+ }}
+
+
+ {{#if choice.icon}}
+ {{d-icon choice.icon}}
+ {{/if}}
+
+ {{choice.label}}
+
+
+ {{#if choice.extraLabel}}
+
+ {{/if}}
+
+
+
+ {{choice.description}}
+
+
+{{/each}}
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-text.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-text.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-textarea.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-textarea.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-field.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-image-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-image-preview.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-image-preview.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-image-preview.hbs
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-step.hbs
similarity index 100%
rename from app/assets/javascripts/wizard/templates/components/wizard-step.hbs
rename to app/assets/javascripts/wizard/addon/templates/components/wizard-step.hbs
diff --git a/app/assets/javascripts/wizard/addon/templates/step.hbs b/app/assets/javascripts/wizard/addon/templates/step.hbs
new file mode 100644
index 00000000000..69bacff68d4
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/templates/step.hbs
@@ -0,0 +1,6 @@
+{{wizard-step
+ step=step
+ wizard=wizard
+ goNext=(action "goNext")
+ goBack=(action "goBack")
+}}
diff --git a/app/assets/javascripts/wizard/addon/templates/wizard.hbs b/app/assets/javascripts/wizard/addon/templates/wizard.hbs
new file mode 100644
index 00000000000..3449dd35754
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/templates/wizard.hbs
@@ -0,0 +1,46 @@
+
+ {{#if showCanvas}}
+ {{wizard-canvas}}
+ {{/if}}
+
+
+
+ {{outlet}}
+
+ {{!-- Load all font styles --}}
+
+ {{#each fontClasses as |fontClass|}}
+
+
+ {{/each}}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js b/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js
new file mode 100644
index 00000000000..54d8a9fd8ee
--- /dev/null
+++ b/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js
@@ -0,0 +1,59 @@
+export default function (helpers) {
+ const { parsePostData, response } = helpers;
+
+ this.get("/wizard.json", () => {
+ return response({
+ wizard: {
+ start: "hello-world",
+ completed: true,
+ steps: [
+ {
+ id: "hello-world",
+ title: "hello there",
+ index: 0,
+ description: "hello!",
+ fields: [
+ {
+ id: "full_name",
+ type: "text",
+ required: true,
+ description: "Your name",
+ },
+ ],
+ next: "second-step",
+ },
+ {
+ id: "second-step",
+ title: "Second step",
+ index: 1,
+ fields: [{ id: "some-title", type: "text" }],
+ previous: "hello-world",
+ next: "last-step",
+ },
+ {
+ id: "last-step",
+ index: 2,
+ fields: [
+ { id: "snack", type: "dropdown", required: true },
+ { id: "theme-preview", type: "component" },
+ { id: "an-image", type: "image" },
+ ],
+ previous: "second-step",
+ },
+ ],
+ },
+ });
+ });
+
+ this.put("/wizard/steps/:id", (request) => {
+ const body = parsePostData(request.requestBody);
+
+ if (body.fields.full_name === "Server Fail") {
+ return response(422, {
+ errors: [{ field: "full_name", description: "Invalid name" }],
+ });
+ } else {
+ return response(200, { success: true });
+ }
+ });
+}
diff --git a/app/assets/javascripts/wizard/app/.gitkeep b/app/assets/javascripts/wizard/app/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/app/assets/javascripts/wizard/components/radio-button.js b/app/assets/javascripts/wizard/components/radio-button.js
deleted file mode 100644
index af16d49236b..00000000000
--- a/app/assets/javascripts/wizard/components/radio-button.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { observes, on } from "discourse-common/utils/decorators";
-import Component from "@ember/component";
-import { next } from "@ember/runloop";
-
-export default Component.extend({
- tagName: "label",
-
- click(e) {
- e.preventDefault();
- this.onChange(this.radioValue);
- },
-
- @observes("value")
- @on("init")
- updateVal() {
- const checked = this.value === this.radioValue;
- next(
- () => (this.element.querySelector("input[type=radio]").checked = checked)
- );
- },
-});
diff --git a/app/assets/javascripts/wizard/controllers/application.js b/app/assets/javascripts/wizard/controllers/application.js
deleted file mode 100644
index d0c86664b39..00000000000
--- a/app/assets/javascripts/wizard/controllers/application.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import Controller from "@ember/controller";
-import { dasherize } from "@ember/string";
-import discourseComputed from "discourse-common/utils/decorators";
-
-export default Controller.extend({
- currentStepId: null,
-
- @discourseComputed("currentStepId")
- showCanvas(currentStepId) {
- return currentStepId === "finished";
- },
-
- @discourseComputed("model")
- fontClasses(model) {
- const fontsStep = model.steps.findBy("id", "styling");
- if (!fontsStep) {
- return [];
- }
-
- const fontField = fontsStep.get("fieldsById.body_font");
- return fontField.choices.map(
- (choice) => `body-font-${dasherize(choice.id)}`
- );
- },
-});
diff --git a/app/assets/javascripts/wizard/controllers/step.js b/app/assets/javascripts/wizard/controllers/step.js
deleted file mode 100644
index 17c08fcec98..00000000000
--- a/app/assets/javascripts/wizard/controllers/step.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import getUrl from "discourse-common/lib/get-url";
-import Controller from "@ember/controller";
-import { action } from "@ember/object";
-
-export default Controller.extend({
- wizard: null,
- step: null,
-
- @action
- goNext(response) {
- const next = this.get("step.next");
- if (response && response.refresh_required) {
- if (this.get("step.id") === "locale") {
- document.location = getUrl(`/wizard/steps/${next}`);
- return;
- } else {
- this.send("refreshRoute");
- }
- }
- if (response && response.success) {
- this.transitionToRoute("step", next);
- }
- },
-
- @action
- goBack() {
- this.transitionToRoute("step", this.get("step.previous"));
- },
-});
diff --git a/app/assets/javascripts/wizard/ember-cli-build.js b/app/assets/javascripts/wizard/ember-cli-build.js
new file mode 100644
index 00000000000..81a8a5e9aa3
--- /dev/null
+++ b/app/assets/javascripts/wizard/ember-cli-build.js
@@ -0,0 +1,9 @@
+"use strict";
+
+const EmberAddon = require("ember-cli/lib/broccoli/ember-addon");
+
+module.exports = function (defaults) {
+ let app = new EmberAddon(defaults, {});
+
+ return app.toTree();
+};
diff --git a/app/assets/javascripts/wizard/index.js b/app/assets/javascripts/wizard/index.js
new file mode 100644
index 00000000000..f29529cc51a
--- /dev/null
+++ b/app/assets/javascripts/wizard/index.js
@@ -0,0 +1,20 @@
+"use strict";
+
+const calculateCacheKeyForTree = require("calculate-cache-key-for-tree");
+
+module.exports = {
+ name: require("./package").name,
+ treeForAddon(tree) {
+ let app = this._findHost();
+ app.options.wizardTree = this._super.treeForAddon.call(this, tree);
+ return;
+ },
+
+ cacheKeyForTree(tree) {
+ return calculateCacheKeyForTree(tree, this);
+ },
+
+ isDevelopingAddon() {
+ return true;
+ },
+};
diff --git a/app/assets/javascripts/wizard/initializers/load-helpers.js b/app/assets/javascripts/wizard/initializers/load-helpers.js
deleted file mode 100644
index cf14d2bbfc3..00000000000
--- a/app/assets/javascripts/wizard/initializers/load-helpers.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { registerHelpers } from "discourse-common/lib/helpers";
-
-export default {
- name: "load-helpers",
-
- initialize(application) {
- Object.keys(requirejs.entries).forEach((entry) => {
- if (/\/helpers\//.test(entry)) {
- requirejs(entry, null, null, true);
- }
- });
- registerHelpers(application);
- },
-};
diff --git a/app/assets/javascripts/wizard/lib/ajax.js b/app/assets/javascripts/wizard/lib/ajax.js
deleted file mode 100644
index 3889069f71e..00000000000
--- a/app/assets/javascripts/wizard/lib/ajax.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Promise } from "rsvp";
-import getUrl from "discourse-common/lib/get-url";
-import jQuery from "jquery";
-import { run } from "@ember/runloop";
-
-let token;
-
-export function getToken() {
- if (!token) {
- token = document.querySelector('meta[name="csrf-token"]')?.content;
- }
-
- return token;
-}
-
-export function ajax(args) {
- let url;
-
- if (arguments.length === 2) {
- url = arguments[0];
- args = arguments[1];
- } else {
- url = args.url;
- }
-
- return new Promise((resolve, reject) => {
- args.headers = { "X-CSRF-Token": getToken() };
- args.success = (data) => run(null, resolve, data);
- args.error = (xhr) => run(null, reject, xhr);
- args.url = getUrl(url);
- jQuery.ajax(args);
- });
-}
diff --git a/app/assets/javascripts/wizard/package.json b/app/assets/javascripts/wizard/package.json
new file mode 100644
index 00000000000..623421f1348
--- /dev/null
+++ b/app/assets/javascripts/wizard/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "wizard",
+ "version": "1.0.0",
+ "description": "Discourse's setup wizard",
+ "author": "Discourse",
+ "license": "MIT",
+ "keywords": [
+ "ember-addon"
+ ],
+ "repository": "",
+ "scripts": {
+ "build": "ember build",
+ "lint:hbs": "ember-template-lint .",
+ "lint:js": "eslint .",
+ "start": "ember serve"
+ },
+ "dependencies": {
+ "ember-auto-import": "^2.2.4",
+ "ember-cli-babel": "^7.13.0",
+ "ember-cli-htmlbars": "^4.2.0",
+ "xss": "^1.0.8",
+ "webpack": "^5.67.0"
+ },
+ "devDependencies": {
+ "@ember/optional-features": "^1.1.0",
+ "@glimmer/component": "^1.0.0",
+ "babel-eslint": "^10.0.3",
+ "broccoli-asset-rev": "^3.0.0",
+ "ember-cli": "~3.25.3",
+ "ember-cli-dependency-checker": "^3.2.0",
+ "ember-cli-eslint": "^5.1.0",
+ "ember-cli-inject-live-reload": "^2.0.1",
+ "ember-cli-sri": "^2.1.1",
+ "ember-cli-template-lint": "^1.0.0-beta.3",
+ "ember-cli-uglify": "^3.0.0",
+ "ember-disable-prototype-extensions": "^1.1.3",
+ "ember-export-application-global": "^2.0.1",
+ "ember-load-initializers": "^2.1.1",
+ "ember-maybe-import-regenerator": "^0.1.6",
+ "ember-resolver": "^7.0.0",
+ "ember-source": "~3.15.0",
+ "ember-source-channel-url": "^2.0.1",
+ "ember-try": "^2.0.0",
+ "eslint": "^7.27.0",
+ "eslint-plugin-ember": "^7.7.1",
+ "eslint-plugin-node": "^10.0.0",
+ "loader.js": "^4.7.0"
+ },
+ "engines": {
+ "node": "12.* || 14.* || >= 16",
+ "npm": "please-use-yarn",
+ "yarn": ">= 1.21.1"
+ },
+ "ember": {
+ "edition": "default"
+ }
+}
diff --git a/app/assets/javascripts/wizard/router.js b/app/assets/javascripts/wizard/router.js
deleted file mode 100644
index f9922077374..00000000000
--- a/app/assets/javascripts/wizard/router.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import EmberRouter from "@ember/routing/router";
-import getUrl from "discourse-common/lib/get-url";
-import { isTesting } from "discourse-common/config/environment";
-
-const Router = EmberRouter.extend({
- rootURL: getUrl("/wizard/"),
- location: isTesting() ? "none" : "history",
-});
-
-Router.map(function () {
- this.route("step", { path: "/steps/:step_id" });
-});
-
-export default Router;
diff --git a/app/assets/javascripts/wizard/routes/application.js b/app/assets/javascripts/wizard/routes/application.js
deleted file mode 100644
index 26bf3ff08ff..00000000000
--- a/app/assets/javascripts/wizard/routes/application.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import Route from "@ember/routing/route";
-import { findWizard } from "wizard/models/wizard";
-import { action } from "@ember/object";
-
-export default Route.extend({
- model() {
- return findWizard();
- },
-
- @action
- refreshRoute() {
- this.refresh();
- },
-});
diff --git a/app/assets/javascripts/wizard/routes/index.js b/app/assets/javascripts/wizard/routes/index.js
deleted file mode 100644
index ccebc21a263..00000000000
--- a/app/assets/javascripts/wizard/routes/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Route from "@ember/routing/route";
-export default Route.extend({
- beforeModel() {
- const appModel = this.modelFor("application");
- this.replaceWith("step", appModel.start);
- },
-});
diff --git a/app/assets/javascripts/wizard/routes/step.js b/app/assets/javascripts/wizard/routes/step.js
deleted file mode 100644
index ed1bef9d904..00000000000
--- a/app/assets/javascripts/wizard/routes/step.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import Route from "@ember/routing/route";
-export default Route.extend({
- model(params) {
- const allSteps = this.modelFor("application").steps;
- const step = allSteps.findBy("id", params.step_id);
- return step ? step : allSteps[0];
- },
-
- setupController(controller, step) {
- this.controllerFor("application").set("currentStepId", step.get("id"));
-
- controller.setProperties({
- step,
- wizard: this.modelFor("application"),
- });
- },
-});
diff --git a/app/assets/javascripts/wizard/templates/application.hbs b/app/assets/javascripts/wizard/templates/application.hbs
deleted file mode 100644
index 54fb74e4cdd..00000000000
--- a/app/assets/javascripts/wizard/templates/application.hbs
+++ /dev/null
@@ -1,43 +0,0 @@
-{{#if showCanvas}}
- {{wizard-canvas}}
-{{/if}}
-
-
-
- {{outlet}}
-
- {{!-- Load all font styles --}}
-
- {{#each fontClasses as |fontClass|}}
-
-
- {{/each}}
-
-
-
-
diff --git a/app/assets/javascripts/wizard/templates/components/radio-button.hbs b/app/assets/javascripts/wizard/templates/components/radio-button.hbs
deleted file mode 100644
index 2974b5ebece..00000000000
--- a/app/assets/javascripts/wizard/templates/components/radio-button.hbs
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- {{#if icon}}
- {{d-icon icon}}
- {{/if}}
- {{label}}
-
- {{#if extraLabel}}
-
- {{/if}}
-
-
- {{description}}
-
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs
deleted file mode 100644
index 8994de0de4e..00000000000
--- a/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs
+++ /dev/null
@@ -1,11 +0,0 @@
-{{#each field.choices as |c|}}
-
- {{radio-button value=field.value
- radioValue=c.id
- label=c.label
- extraLabel=c.extra_label
- icon=c.icon
- description=c.description
- onChange=(action "changed")}}
-
-{{/each}}
diff --git a/app/assets/javascripts/wizard/templates/step.hbs b/app/assets/javascripts/wizard/templates/step.hbs
deleted file mode 100644
index 9d42be41e50..00000000000
--- a/app/assets/javascripts/wizard/templates/step.hbs
+++ /dev/null
@@ -1 +0,0 @@
-{{wizard-step step=step wizard=wizard goNext=(action "goNext") goBack=(action "goBack")}}
diff --git a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js b/app/assets/javascripts/wizard/test/acceptance/wizard-test.js
deleted file mode 100644
index ea550cf2708..00000000000
--- a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers";
-import { module, test } from "qunit";
-import { run } from "@ember/runloop";
-import startApp from "wizard/test/helpers/start-app";
-
-let wizard;
-module("Acceptance: wizard", {
- beforeEach() {
- wizard = startApp();
- },
-
- afterEach() {
- run(wizard, "destroy");
- },
-});
-
-function exists(selector) {
- return document.querySelector(selector) !== null;
-}
-
-test("Wizard starts", async function (assert) {
- await visit("/");
- assert.ok(exists(".wizard-column-contents"));
- assert.strictEqual(currentRouteName(), "step");
-});
-
-test("Going back and forth in steps", async function (assert) {
- await visit("/steps/hello-world");
- assert.ok(exists(".wizard-step"));
- assert.ok(
- exists(".wizard-step-hello-world"),
- "it adds a class for the step id"
- );
- assert.ok(!exists(".wizard-btn.finish"), "can’t finish on first step");
- assert.ok(exists(".wizard-progress"));
- assert.ok(exists(".wizard-step-title"));
- assert.ok(exists(".wizard-step-description"));
- assert.ok(
- !exists(".invalid .field-full-name"),
- "don't show it as invalid until the user does something"
- );
- assert.ok(exists(".wizard-field .field-description"));
- assert.ok(!exists(".wizard-btn.back"));
- assert.ok(!exists(".wizard-field .field-error-description"));
-
- // invalid data
- await click(".wizard-btn.next");
- assert.ok(exists(".invalid .field-full-name"));
-
- // server validation fail
- await fillIn("input.field-full-name", "Server Fail");
- await click(".wizard-btn.next");
- assert.ok(exists(".invalid .field-full-name"));
- assert.ok(exists(".wizard-field .field-error-description"));
-
- // server validation ok
- await fillIn("input.field-full-name", "Evil Trout");
- await click(".wizard-btn.next");
- assert.ok(!exists(".wizard-field .field-error-description"));
- assert.ok(!exists(".wizard-step-description"));
- assert.ok(
- exists(".wizard-btn.finish"),
- "shows finish on an intermediate step"
- );
-
- await click(".wizard-btn.next");
- assert.ok(exists(".select-kit.field-snack"), "went to the next step");
- assert.ok(exists(".preview-area"), "renders the component field");
- assert.ok(exists(".wizard-btn.done"), "last step shows a done button");
- assert.ok(exists(".action-link.back"), "shows the back button");
- assert.ok(!exists(".wizard-step-title"));
- assert.ok(!exists(".wizard-btn.finish"), "can’t finish on last step");
-
- await click(".action-link.back");
- assert.ok(exists(".wizard-step-title"));
- assert.ok(exists(".wizard-btn.next"));
- assert.ok(!exists(".wizard-prev"));
-});
diff --git a/app/assets/javascripts/wizard/test/components/invite-list-test.js b/app/assets/javascripts/wizard/test/components/invite-list-test.js
deleted file mode 100644
index 2acfe5071c0..00000000000
--- a/app/assets/javascripts/wizard/test/components/invite-list-test.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import { componentTest } from "wizard/test/helpers/component-test";
-import { moduleForComponent } from "ember-qunit";
-import { click, fillIn } from "@ember/test-helpers";
-
-moduleForComponent("invite-list", { integration: true });
-
-componentTest("can add users", {
- template: `{{invite-list field=field}}`,
-
- beforeEach() {
- this.set("field", {});
- },
-
- async test(assert) {
- assert.ok(
- document.querySelectorAll(".users-list .invite-list-user").length === 0,
- "no users at first"
- );
- assert.ok(
- document.querySelectorAll(".new-user .invalid").length === 0,
- "not invalid at first"
- );
-
- const firstVal = JSON.parse(this.get("field.value"));
- assert.strictEqual(firstVal.length, 0, "empty JSON at first");
-
- assert.ok(
- this.get("field.warning"),
- "it has a warning since no users were added"
- );
-
- await click(".add-user");
- assert.ok(
- document.querySelectorAll(".users-list .invite-list-user").length === 0,
- "doesn't add a blank user"
- );
- assert.ok(document.querySelectorAll(".new-user .invalid").length === 1);
-
- await fillIn(".invite-email", "eviltrout@example.com");
- await click(".add-user");
-
- assert.ok(
- document.querySelectorAll(".users-list .invite-list-user").length === 1,
- "adds the user"
- );
- assert.ok(document.querySelectorAll(".new-user .invalid").length === 0);
-
- const val = JSON.parse(this.get("field.value"));
- assert.strictEqual(val.length, 1);
- assert.strictEqual(
- val[0].email,
- "eviltrout@example.com",
- "adds the email to the JSON"
- );
- assert.ok(val[0].role.length, "adds the role to the JSON");
- assert.ok(!this.get("field.warning"), "no warning once the user is added");
-
- await fillIn(".invite-email", "eviltrout@example.com");
- await click(".add-user");
-
- assert.ok(
- document.querySelectorAll(".users-list .invite-list-user").length === 1,
- "can't add the same user twice"
- );
- assert.ok(document.querySelectorAll(".new-user .invalid").length === 1);
-
- await fillIn(".invite-email", "not-an-email");
- await click(".add-user");
-
- assert.ok(
- document.querySelectorAll(".users-list .invite-list-user").length === 1,
- "won't add an invalid email"
- );
- assert.ok(document.querySelectorAll(".new-user .invalid").length === 1);
-
- await click(".invite-list .invite-list-user:nth-of-type(1) .remove-user");
- assert.ok(
- document.querySelectorAll(".users-list .invite-list-user").length === 0,
- "removed the user"
- );
- },
-});
diff --git a/app/assets/javascripts/wizard/test/helpers/component-test.js b/app/assets/javascripts/wizard/test/helpers/component-test.js
deleted file mode 100644
index e7d5f7c2288..00000000000
--- a/app/assets/javascripts/wizard/test/helpers/component-test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* eslint-disable no-undef */
-import initializer from "wizard/initializers/load-helpers";
-import { test } from "qunit";
-
-export function componentTest(name, opts) {
- opts = opts || {};
-
- test(name, function (assert) {
- initializer.initialize(this.registry);
-
- if (opts.beforeEach) {
- opts.beforeEach.call(this);
- }
-
- andThen(() => this.render(opts.template));
- andThen(() => opts.test.call(this, assert));
- });
-}
diff --git a/app/assets/javascripts/wizard/test/helpers/start-app.js b/app/assets/javascripts/wizard/test/helpers/start-app.js
deleted file mode 100644
index e49d315bb2f..00000000000
--- a/app/assets/javascripts/wizard/test/helpers/start-app.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import Wizard from "wizard/wizard";
-import initializer from "wizard/initializers/load-helpers";
-import { run } from "@ember/runloop";
-
-let app;
-let started = false;
-
-export default function () {
- run(() => (app = Wizard.create({ rootElement: "#ember-testing" })));
-
- if (!started) {
- initializer.initialize(app);
- app.start();
- started = true;
- }
- app.setupForTesting();
- app.injectTestHelpers();
- return app;
-}
diff --git a/app/assets/javascripts/wizard/test/models/wizard-field-test.js b/app/assets/javascripts/wizard/test/models/wizard-field-test.js
deleted file mode 100644
index 90eda2bb14c..00000000000
--- a/app/assets/javascripts/wizard/test/models/wizard-field-test.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import WizardField from "wizard/models/wizard-field";
-import { moduleFor } from "ember-qunit";
-import { test } from "qunit";
-
-moduleFor("model:wizard-field");
-
-test("basic state", function (assert) {
- const w = WizardField.create({ type: "text" });
- assert.ok(w.get("unchecked"));
- assert.ok(!w.get("valid"));
- assert.ok(!w.get("invalid"));
-});
-
-test("text - required - validation", function (assert) {
- const w = WizardField.create({ type: "text", required: true });
- assert.ok(w.get("unchecked"));
-
- w.check();
- assert.ok(!w.get("unchecked"));
- assert.ok(!w.get("valid"));
- assert.ok(w.get("invalid"));
-
- w.set("value", "a value");
- w.check();
- assert.ok(!w.get("unchecked"));
- assert.ok(w.get("valid"));
- assert.ok(!w.get("invalid"));
-});
-
-test("text - optional - validation", function (assert) {
- const f = WizardField.create({ type: "text" });
- assert.ok(f.get("unchecked"));
-
- f.check();
- assert.ok(f.get("valid"));
-});
diff --git a/app/assets/javascripts/wizard/test/test_helper.js b/app/assets/javascripts/wizard/test/test_helper.js
deleted file mode 100644
index 39ed80b0dd5..00000000000
--- a/app/assets/javascripts/wizard/test/test_helper.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// discourse-skip-module
-/*global document, Logster, QUnit */
-
-//= require env
-//= require jquery.debug
-//= require ember.debug
-//= require locales/i18n
-//= require locales/en
-//= require route-recognizer
-//= require fake_xml_http_request
-//= require pretender
-//= require qunit
-//= require ember-qunit
-//= require discourse-loader
-//= require jquery.debug
-//= require handlebars
-//= require ember-template-compiler
-//= require wizard-application
-//= require wizard-vendor
-//= require_tree ./helpers
-//= require_tree ./acceptance
-//= require_tree ./models
-//= require_tree ./components
-//= require ./wizard-pretender
-//= require test-shims
-
-document.addEventListener("DOMContentLoaded", function () {
- document.body.insertAdjacentHTML(
- "afterbegin",
- `
-
-
- `
- );
-});
-
-if (window.Logster) {
- Logster.enabled = false;
-} else {
- window.Logster = { enabled: false };
-}
-// eslint-disable-next-line no-undef
-Ember.Test.adapter = window.QUnitAdapter.create();
-
-let createPretendServer = requirejs(
- "wizard/test/wizard-pretender",
- null,
- null,
- false
-).default;
-
-let server;
-
-const queryParams = new URLSearchParams(window.location.search);
-
-if (queryParams.get("qunit_disable_auto_start") === "1") {
- QUnit.config.autostart = false;
-}
-
-QUnit.testStart(function () {
- server = createPretendServer();
-});
-
-QUnit.testDone(function () {
- server.shutdown();
-});
-
-let _testApp = requirejs("wizard/test/helpers/start-app").default();
-let _buildResolver = requirejs("discourse-common/resolver").buildResolver;
-window.setResolver(_buildResolver("wizard").create({ namespace: _testApp }));
-
-Object.keys(requirejs.entries).forEach(function (entry) {
- if (/\-test/.test(entry)) {
- requirejs(entry, null, null, true);
- }
-});
diff --git a/app/assets/javascripts/wizard/test/wizard-pretender.js b/app/assets/javascripts/wizard/test/wizard-pretender.js
deleted file mode 100644
index e9dccfb3fbf..00000000000
--- a/app/assets/javascripts/wizard/test/wizard-pretender.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import Pretender from "pretender";
-
-// TODO: This file has some copied and pasted functions from `create-pretender` - would be good
-// to centralize that code at some point.
-
-function parsePostData(query) {
- const result = {};
- query.split("&").forEach(function (part) {
- const item = part.split("=");
- const firstSeg = decodeURIComponent(item[0]);
- const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg);
-
- const val = decodeURIComponent(item[1]).replace(/\+/g, " ");
- if (m) {
- result[m[1]] = result[m[1]] || {};
- result[m[1]][m[2]] = val;
- } else {
- result[firstSeg] = val;
- }
- });
- return result;
-}
-
-function response(code, obj) {
- if (typeof code === "object") {
- obj = code;
- code = 200;
- }
- return [code, { "Content-Type": "application/json" }, obj];
-}
-
-export default function () {
- const server = new Pretender(function () {
- this.get("/wizard.json", () => {
- return response(200, {
- wizard: {
- start: "hello-world",
- completed: true,
- steps: [
- {
- id: "hello-world",
- title: "hello there",
- index: 0,
- description: "hello!",
- fields: [
- {
- id: "full_name",
- type: "text",
- required: true,
- description: "Your name",
- },
- ],
- next: "second-step",
- },
- {
- id: "second-step",
- title: "Second step",
- index: 1,
- fields: [{ id: "some-title", type: "text" }],
- previous: "hello-world",
- next: "last-step",
- },
- {
- id: "last-step",
- index: 2,
- fields: [
- { id: "snack", type: "dropdown", required: true },
- { id: "theme-preview", type: "component" },
- { id: "an-image", type: "image" },
- ],
- previous: "second-step",
- },
- ],
- },
- });
- });
-
- this.put("/wizard/steps/:id", (request) => {
- const body = parsePostData(request.requestBody);
-
- if (body.fields.full_name === "Server Fail") {
- return response(422, {
- errors: [{ field: "full_name", description: "Invalid name" }],
- });
- } else {
- return response(200, { success: true });
- }
- });
- });
-
- server.prepareBody = function (body) {
- if (body && typeof body === "object") {
- return JSON.stringify(body);
- }
- return body;
- };
-
- server.unhandledRequest = function (verb, path) {
- const error =
- "Unhandled request in test environment: " + path + " (" + verb + ")";
- window.console.error(error);
- throw error;
- };
-
- return server;
-}
diff --git a/app/assets/javascripts/wizard/wizard.js b/app/assets/javascripts/wizard/wizard.js
deleted file mode 100644
index 8e84fe18f5f..00000000000
--- a/app/assets/javascripts/wizard/wizard.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import Application from "@ember/application";
-import { buildResolver } from "discourse-common/resolver";
-
-export default Application.extend({
- rootElement: "#wizard-main",
- Resolver: buildResolver("wizard"),
-
- start() {
- // required for select kit to work without Ember CLI
- // eslint-disable-next-line no-undef
- Object.keys(Ember.TEMPLATES).forEach((k) => {
- if (k.indexOf("select-kit") === 0) {
- // eslint-disable-next-line no-undef
- let template = Ember.TEMPLATES[k];
- define(k, () => template);
- }
- });
-
- Object.keys(requirejs._eak_seen).forEach((key) => {
- if (/\/initializers\//.test(key)) {
- const module = requirejs(key, null, null, true);
- if (!module) {
- throw new Error(key + " must export an initializer.");
- }
- this.initializer(module.default);
- }
- });
- },
-});
diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss
index bf154a0b703..22aa6e95259 100644
--- a/app/assets/stylesheets/wizard.scss
+++ b/app/assets/stylesheets/wizard.scss
@@ -10,7 +10,7 @@ $bubbles-mask: svg-uri(
'