diff --git a/frontend/src/metabase-types/api/mocks/index.ts b/frontend/src/metabase-types/api/mocks/index.ts
index 8ce1fbbc0fb2fa3cf9dd19a1d233b90918ff072c..c3bd4fa37ef284536ed598403cb43a2323f79c43 100644
--- a/frontend/src/metabase-types/api/mocks/index.ts
+++ b/frontend/src/metabase-types/api/mocks/index.ts
@@ -8,6 +8,7 @@ export * from "./dashboard";
 export * from "./database";
 export * from "./dataset";
 export * from "./field";
+export * from "./group";
 export * from "./metric";
 export * from "./models";
 export * from "./modelIndexes";
diff --git a/frontend/src/metabase/reducers-main.js b/frontend/src/metabase/reducers-main.js
index 2d6b0095d390c31be5b59025bef90ef467a9ea9d..94e6730da8006d4a09cdc839f5d4652ae7014fb2 100644
--- a/frontend/src/metabase/reducers-main.js
+++ b/frontend/src/metabase/reducers-main.js
@@ -8,7 +8,7 @@ import { PLUGIN_REDUCERS } from "metabase/plugins";
 import admin from "metabase/admin/admin";
 
 /* setup */
-import * as setup from "metabase/setup/reducers";
+import { reducer as setup } from "metabase/setup/reducers";
 
 /* dashboards */
 import dashboard from "metabase/dashboard/reducers";
@@ -48,7 +48,7 @@ export default {
   qb: combineReducers(qb),
   reference,
   revisions,
-  setup: combineReducers(setup),
+  setup,
   admin,
   plugins: combineReducers(PLUGIN_REDUCERS),
 };
diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx
index 60466ae598e5f291bbf6bc8b5358ace5d3e341db..41709d61bddf3f3a4c2aa0fc2f76ce95a695521d 100644
--- a/frontend/src/metabase/routes.jsx
+++ b/frontend/src/metabase/routes.jsx
@@ -37,7 +37,7 @@ import CollectionPermissionsModal from "metabase/admin/permissions/components/Co
 import UserCollectionList from "metabase/containers/UserCollectionList";
 
 import PulseEditApp from "metabase/pulse/containers/PulseEditApp";
-import SetupApp from "metabase/setup/containers/SetupApp";
+import { Setup } from "metabase/setup/components/Setup";
 
 import NewModelOptions from "metabase/models/containers/NewModelOptions";
 
@@ -98,7 +98,7 @@ export const getRoutes = store => (
     {/* SETUP */}
     <Route
       path="/setup"
-      component={SetupApp}
+      component={Setup}
       onEnter={(nextState, replace) => {
         if (MetabaseSettings.hasUserSetup()) {
           replace("/");
diff --git a/frontend/src/metabase/setup/actions.ts b/frontend/src/metabase/setup/actions.ts
index e3ac67b4152df397e87894bba19f557688436e8c..212964f88373f87a73a2331dc1d5c61c3f71bda4 100644
--- a/frontend/src/metabase/setup/actions.ts
+++ b/frontend/src/metabase/setup/actions.ts
@@ -1,125 +1,198 @@
-import { createAction } from "redux-actions";
-import { getIn } from "icepick";
-import { SetupApi, UtilApi } from "metabase/services";
-import { createThunkAction } from "metabase/lib/redux";
-import { loadLocalization } from "metabase/lib/i18n";
+import { createAction, createAsyncThunk } from "@reduxjs/toolkit";
+import { SetupApi } from "metabase/services";
 import MetabaseSettings from "metabase/lib/settings";
+import { loadLocalization } from "metabase/lib/i18n";
 import { DatabaseData } from "metabase-types/api";
-import { Locale } from "metabase-types/store";
-import { getUserToken, getDefaultLocale, getLocales } from "./utils";
-
-export const SET_STEP = "metabase/setup/SET_STEP";
-export const setStep = createAction(SET_STEP);
-
-export const SET_LOCALE = "metabase/setup/SET_LOCALE";
-export const SET_LOCALE_LOADED = "metabase/setup/SET_LOCALE_LOADED";
-export const setLocale = createThunkAction(
-  SET_LOCALE_LOADED,
-  (locale: Locale) => async (dispatch: any) => {
-    dispatch({ type: SET_LOCALE, payload: locale });
-    await loadLocalization(locale.code);
-  },
-);
-
-export const SET_USER = "metabase/setup/SET_USER";
-export const setUser = createAction(SET_USER);
-
-export const SET_DATABASE_ENGINE = "metabase/setup/SET_DATABASE_ENGINE";
-export const setDatabaseEngine = createAction(SET_DATABASE_ENGINE);
-
-export const SET_DATABASE = "metabase/setup/SET_DATABASE";
-export const setDatabase = createAction(SET_DATABASE);
-
-export const SET_INVITE = "metabase/setup/SET_INVITE";
-export const setInvite = createAction(SET_INVITE);
-
-export const SET_TRACKING = "metabase/setup/SET_TRACKING";
-export const setTracking = createAction(SET_TRACKING);
+import { InviteInfo, Locale, State, UserInfo } from "metabase-types/store";
+import {
+  trackAddDataLaterClicked,
+  trackDatabaseSelected,
+  trackDatabaseStepCompleted,
+  trackTrackingChanged,
+  trackUserStepCompleted,
+  trackWelcomeStepCompleted,
+} from "./analytics";
+import {
+  getAvailableLocales,
+  getDatabase,
+  getInvite,
+  getIsTrackingAllowed,
+  getLocale,
+  getSetupToken,
+  getUser,
+} from "./selectors";
+import { getDefaultLocale, getLocales, getUserToken } from "./utils";
+
+interface ThunkConfig {
+  state: State;
+}
 
 export const LOAD_USER_DEFAULTS = "metabase/setup/LOAD_USER_DEFAULTS";
-export const loadUserDefaults = createThunkAction(
+export const loadUserDefaults = createAsyncThunk(
   LOAD_USER_DEFAULTS,
-  () => async (dispatch: any) => {
+  async (): Promise<UserInfo | undefined> => {
     const token = getUserToken();
     if (token) {
       const defaults = await SetupApi.user_defaults({ token });
-      dispatch(setUser(defaults.user));
+      return defaults.user;
     }
   },
 );
 
 export const LOAD_LOCALE_DEFAULTS = "metabase/setup/LOAD_LOCALE_DEFAULTS";
-export const loadLocaleDefaults = createThunkAction(
-  LOAD_LOCALE_DEFAULTS,
-  () => async (dispatch: any) => {
-    const data = MetabaseSettings.get("available-locales") || [];
-    const locale = getDefaultLocale(getLocales(data));
-    await dispatch(setLocale(locale));
+export const loadLocaleDefaults = createAsyncThunk<
+  Locale | undefined,
+  void,
+  ThunkConfig
+>(LOAD_LOCALE_DEFAULTS, async (_, { getState }) => {
+  const data = getAvailableLocales(getState());
+  const locale = getDefaultLocale(getLocales(data));
+  if (locale) {
+    await loadLocalization(locale.code);
+  }
+  return locale;
+});
+
+export const LOAD_DEFAULTS = "metabase/setup/LOAD_DEFAULTS";
+export const loadDefaults = createAsyncThunk<void, void, ThunkConfig>(
+  LOAD_DEFAULTS,
+  (_, { dispatch }) => {
+    dispatch(loadUserDefaults());
+    dispatch(loadLocaleDefaults());
   },
 );
 
-export const validatePassword = async (password: string) => {
-  const error = MetabaseSettings.passwordComplexityDescription(password);
-  if (error) {
-    return error;
-  }
+export const SELECT_STEP = "metabase/setup/SUBMIT_WELCOME_STEP";
+export const selectStep = createAction<number>(SELECT_STEP);
 
-  try {
-    await UtilApi.password_check({ password });
-  } catch (error) {
-    return getIn(error, ["data", "errors", "password"]);
-  }
-};
+export const SUBMIT_WELCOME = "metabase/setup/SUBMIT_WELCOME_STEP";
+export const submitWelcome = createAsyncThunk(SUBMIT_WELCOME, () => {
+  trackWelcomeStepCompleted();
+});
+
+export const UPDATE_LOCALE = "metabase/setup/UPDATE_LOCALE";
+export const updateLocale = createAsyncThunk(
+  UPDATE_LOCALE,
+  async (locale: Locale) => {
+    await loadLocalization(locale.code);
+  },
+);
+
+export const SUBMIT_LANGUAGE = "metabase/setup/SUBMIT_LANGUAGE";
+export const submitLanguage = createAction(SUBMIT_LANGUAGE);
+
+export const submitUser = createAsyncThunk(
+  "metabase/setup/SUBMIT_USER_INFO",
+  (_: UserInfo) => {
+    trackUserStepCompleted();
+  },
+);
 
-export const VALIDATE_DATABASE = "metabase/setup/VALIDATE_DATABASE";
-export const validateDatabase = createThunkAction(
-  VALIDATE_DATABASE,
-  (database: DatabaseData) => async () => {
-    await SetupApi.validate_db({
-      token: MetabaseSettings.get("setup-token"),
-      details: database,
-    });
+export const UPDATE_DATABASE_ENGINE = "metabase/setup/UPDATE_DATABASE_ENGINE";
+export const updateDatabaseEngine = createAsyncThunk(
+  UPDATE_DATABASE_ENGINE,
+  (engine?: string) => {
+    if (engine) {
+      trackDatabaseSelected(engine);
+    }
   },
 );
 
+const validateDatabase = async (token: string, database: DatabaseData) => {
+  await SetupApi.validate_db({
+    token,
+    details: database,
+  });
+};
+
 export const SUBMIT_DATABASE = "metabase/setup/SUBMIT_DATABASE";
-export const submitDatabase = createThunkAction(
+export const submitDatabase = createAsyncThunk<
+  DatabaseData,
+  DatabaseData,
+  ThunkConfig
+>(
   SUBMIT_DATABASE,
-  (database: DatabaseData) => async (dispatch: any) => {
+  async (database: DatabaseData, { getState, rejectWithValue }) => {
+    const token = getSetupToken(getState());
     const sslDetails = { ...database.details, ssl: true };
     const sslDatabase = { ...database, details: sslDetails };
     const nonSslDetails = { ...database.details, ssl: false };
     const nonSslDatabase = { ...database, database: nonSslDetails };
 
+    if (!token) {
+      return database;
+    }
+
     try {
-      await dispatch(validateDatabase(sslDatabase));
-      await dispatch(setDatabase(sslDatabase));
-    } catch (error) {
-      await dispatch(validateDatabase(nonSslDatabase));
-      await dispatch(setDatabase(nonSslDatabase));
+      await validateDatabase(token, sslDatabase);
+      trackDatabaseStepCompleted(database.engine);
+      return sslDatabase;
+    } catch (error1) {
+      try {
+        await validateDatabase(token, nonSslDatabase);
+        trackDatabaseStepCompleted(database.engine);
+        return nonSslDatabase;
+      } catch (error2) {
+        return rejectWithValue(error2);
+      }
     }
   },
 );
 
+export const SUBMIT_USER_INVITE = "metabase/setup/SUBMIT_USER_INVITE";
+export const submitUserInvite = createAsyncThunk(
+  SUBMIT_USER_INVITE,
+  (_: InviteInfo) => {
+    trackDatabaseStepCompleted();
+  },
+);
+
+export const SKIP_DATABASE = "metabase/setup/SKIP_DATABASE";
+export const skipDatabase = createAsyncThunk(
+  SKIP_DATABASE,
+  (engine?: string) => {
+    trackDatabaseStepCompleted();
+    trackAddDataLaterClicked(engine);
+  },
+);
+
+export const UPDATE_TRACKING = "metabase/setup/UPDATE_TRACKING";
+export const updateTracking = createAsyncThunk(
+  UPDATE_TRACKING,
+  (isTrackingAllowed: boolean) => {
+    trackTrackingChanged(isTrackingAllowed);
+    MetabaseSettings.set("anon-tracking-enabled", isTrackingAllowed);
+    trackTrackingChanged(isTrackingAllowed);
+  },
+);
+
 export const SUBMIT_SETUP = "metabase/setup/SUBMIT_SETUP";
-export const submitSetup = createThunkAction(
+export const submitSetup = createAsyncThunk<void, void, ThunkConfig>(
   SUBMIT_SETUP,
-  () => async (dispatch: any, getState: any) => {
-    const { setup } = getState();
-    const { locale, user, database, invite, isTrackingAllowed } = setup;
-
-    await SetupApi.create({
-      token: MetabaseSettings.get("setup-token"),
-      user,
-      database,
-      invite,
-      prefs: {
-        site_name: user.site_name,
-        site_locale: locale.code,
-        allow_tracking: isTrackingAllowed.toString(),
-      },
-    });
-
-    MetabaseSettings.set("setup-token", null);
+  async (_, { getState, rejectWithValue }) => {
+    const token = getSetupToken(getState());
+    const locale = getLocale(getState());
+    const user = getUser(getState());
+    const database = getDatabase(getState());
+    const invite = getInvite(getState());
+    const isTrackingAllowed = getIsTrackingAllowed(getState());
+
+    try {
+      await SetupApi.create({
+        token,
+        user,
+        database,
+        invite,
+        prefs: {
+          site_name: user?.site_name,
+          site_locale: locale?.code,
+          allow_tracking: isTrackingAllowed.toString(),
+        },
+      });
+
+      MetabaseSettings.set("setup-token", null);
+    } catch (error) {
+      return rejectWithValue(error);
+    }
   },
 );
diff --git a/frontend/src/metabase/setup/components/ActiveStep/ActiveStep.tsx b/frontend/src/metabase/setup/components/ActiveStep/ActiveStep.tsx
index 5c2aff92a12d8454633deec492beca4222f8b011..80a59680a523484bfd11a8882275237609b0f775 100644
--- a/frontend/src/metabase/setup/components/ActiveStep/ActiveStep.tsx
+++ b/frontend/src/metabase/setup/components/ActiveStep/ActiveStep.tsx
@@ -6,13 +6,13 @@ import {
   StepLabelText,
 } from "./ActiveStep.styled";
 
-export interface ActiveStepProps {
+interface ActiveStepProps {
   title: string;
   label: number;
   children?: ReactNode;
 }
 
-const ActiveStep = ({
+export const ActiveStep = ({
   title,
   label,
   children,
@@ -27,6 +27,3 @@ const ActiveStep = ({
     </StepRoot>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default ActiveStep;
diff --git a/frontend/src/metabase/setup/components/ActiveStep/index.ts b/frontend/src/metabase/setup/components/ActiveStep/index.ts
index 81de9bb107cccd783d7f35b7f50f18414a268b0e..eab397459dbc76227b30fc3637aa8b09736fe1dd 100644
--- a/frontend/src/metabase/setup/components/ActiveStep/index.ts
+++ b/frontend/src/metabase/setup/components/ActiveStep/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./ActiveStep";
+export * from "./ActiveStep";
diff --git a/frontend/src/metabase/setup/components/CloudMigrationHelp/CloudMigrationHelp.tsx b/frontend/src/metabase/setup/components/CloudMigrationHelp/CloudMigrationHelp.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..43f7fdd571460b2772ecc84f0372d99a6e30681f
--- /dev/null
+++ b/frontend/src/metabase/setup/components/CloudMigrationHelp/CloudMigrationHelp.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { t } from "ttag";
+import { useSelector } from "metabase/lib/redux";
+import MetabaseSettings from "metabase/lib/settings";
+import HelpCard from "metabase/components/HelpCard";
+import { COMPLETED_STEP } from "../../constants";
+import { getIsHosted, getIsStepActive } from "../../selectors";
+import { SetupCardContainer } from "../SetupCardContainer";
+
+export const CloudMigrationHelp = () => {
+  const isHosted = useSelector(getIsHosted);
+  const isStepActive = useSelector(state =>
+    getIsStepActive(state, COMPLETED_STEP),
+  );
+  const isVisible = isHosted && isStepActive;
+
+  return (
+    <SetupCardContainer isVisible={isVisible}>
+      <HelpCard
+        title={t`Migrating from self-hosted?`}
+        helpUrl={MetabaseSettings.migrateToCloudGuideUrl()}
+      >{t`Check out our docs for how to migrate your self-hosted instance to Cloud.`}</HelpCard>
+    </SetupCardContainer>
+  );
+};
diff --git a/frontend/src/metabase/setup/components/CloudMigrationHelp/index.ts b/frontend/src/metabase/setup/components/CloudMigrationHelp/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fe4ff8545627767ccf3cd02356cc0e1bd18aeba0
--- /dev/null
+++ b/frontend/src/metabase/setup/components/CloudMigrationHelp/index.ts
@@ -0,0 +1 @@
+export * from "./CloudMigrationHelp";
diff --git a/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.tsx b/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.tsx
index 92e3e595242b7f84a13463905cb6f20a1f17d9ec..16ef4cb505a9e95d904771405c48f0181b3ad4c7 100644
--- a/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.tsx
+++ b/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.tsx
@@ -1,6 +1,9 @@
 import React from "react";
 import { t } from "ttag";
-import NewsletterForm from "../../containers/NewsletterForm";
+import { useSelector } from "metabase/lib/redux";
+import { COMPLETED_STEP } from "../../constants";
+import { getIsStepActive } from "../../selectors";
+import { NewsletterForm } from "../NewsletterForm";
 import {
   StepBody,
   StepFooter,
@@ -8,13 +11,10 @@ import {
   StepTitle,
 } from "./CompletedStep.styled";
 
-export interface CompletedStepProps {
-  isStepActive: boolean;
-}
-
-const CompletedStep = ({
-  isStepActive,
-}: CompletedStepProps): JSX.Element | null => {
+export const CompletedStep = (): JSX.Element | null => {
+  const isStepActive = useSelector(state =>
+    getIsStepActive(state, COMPLETED_STEP),
+  );
   if (!isStepActive) {
     return null;
   }
@@ -35,6 +35,3 @@ const CompletedStep = ({
     </StepRoot>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default CompletedStep;
diff --git a/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.unit.spec.tsx b/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.unit.spec.tsx
index b28048c27a54d1d964f8ba634329b0b392b527d4..5f0d706536f608dd275bdeb397bbece64b584e0e 100644
--- a/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/CompletedStep/CompletedStep.unit.spec.tsx
@@ -1,34 +1,37 @@
 import React from "react";
-import { render, screen } from "@testing-library/react";
-import CompletedStep, { CompletedStepProps } from "./CompletedStep";
+import {
+  createMockSetupState,
+  createMockState,
+} from "metabase-types/store/mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { COMPLETED_STEP, USER_STEP } from "../../constants";
+import { CompletedStep } from "./CompletedStep";
+
+interface SetupOpts {
+  step?: number;
+}
+
+const setup = ({ step = COMPLETED_STEP }: SetupOpts = {}) => {
+  const state = createMockState({
+    setup: createMockSetupState({
+      step,
+    }),
+  });
 
-const NewsletterFormMock = () => <div>Metabase Newsletter</div>;
-jest.mock("../../containers/NewsletterForm", () => NewsletterFormMock);
+  renderWithProviders(<CompletedStep />, { storeInitialState: state });
+};
 
 describe("CompletedStep", () => {
   it("should render in inactive state", () => {
-    const props = getProps({
-      isStepActive: false,
-    });
-
-    render(<CompletedStep {...props} />);
+    setup({ step: USER_STEP });
 
     expect(screen.queryByText("You're all set up!")).not.toBeInTheDocument();
   });
 
   it("should show a newsletter form and a link to the app", () => {
-    const props = getProps({
-      isStepActive: true,
-    });
-
-    render(<CompletedStep {...props} />);
+    setup({ step: COMPLETED_STEP });
 
     expect(screen.getByText("Metabase Newsletter")).toBeInTheDocument();
     expect(screen.getByText("Take me to Metabase")).toBeInTheDocument();
   });
 });
-
-const getProps = (opts?: Partial<CompletedStepProps>): CompletedStepProps => ({
-  isStepActive: false,
-  ...opts,
-});
diff --git a/frontend/src/metabase/setup/components/CompletedStep/index.ts b/frontend/src/metabase/setup/components/CompletedStep/index.ts
index 43cf9c3b966d53ca03603c00a8717aa3bd9c49b8..fb74d8c165fe714b8e1bd312c623a430a5b4bb3a 100644
--- a/frontend/src/metabase/setup/components/CompletedStep/index.ts
+++ b/frontend/src/metabase/setup/components/CompletedStep/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./CompletedStep";
+export * from "./CompletedStep";
diff --git a/frontend/src/metabase/setup/components/DatabaseHelp/DatabaseHelp.tsx b/frontend/src/metabase/setup/components/DatabaseHelp/DatabaseHelp.tsx
index ffedf6700a5749d40b0922b2f4e163e1c57415f3..ca510bc51918263d12a6f37028f4c0fb12d86ff6 100644
--- a/frontend/src/metabase/setup/components/DatabaseHelp/DatabaseHelp.tsx
+++ b/frontend/src/metabase/setup/components/DatabaseHelp/DatabaseHelp.tsx
@@ -1,16 +1,15 @@
 import React from "react";
+import { useSelector } from "metabase/lib/redux";
 import DatabaseHelpCard from "metabase/databases/containers/DatabaseHelpCard";
+import { DATABASE_STEP } from "../../constants";
+import { getDatabaseEngine, getIsStepActive } from "../../selectors";
 import { SetupCardContainer } from "../SetupCardContainer";
 
-export interface DatabaseHelpProps {
-  engine?: string;
-  isStepActive: boolean;
-}
-
-const DatabaseHelp = ({
-  engine,
-  isStepActive,
-}: DatabaseHelpProps): JSX.Element => {
+export const DatabaseHelp = (): JSX.Element => {
+  const engine = useSelector(getDatabaseEngine);
+  const isStepActive = useSelector(state =>
+    getIsStepActive(state, DATABASE_STEP),
+  );
   const isVisible = isStepActive && engine != null;
 
   return (
@@ -19,6 +18,3 @@ const DatabaseHelp = ({
     </SetupCardContainer>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default DatabaseHelp;
diff --git a/frontend/src/metabase/setup/components/DatabaseHelp/index.ts b/frontend/src/metabase/setup/components/DatabaseHelp/index.ts
index c82449309469f225e0c47b85b7fe6bfc6f9592b5..ea7e6f61fe5f52b41ff674c614c2f53164044b30 100644
--- a/frontend/src/metabase/setup/components/DatabaseHelp/index.ts
+++ b/frontend/src/metabase/setup/components/DatabaseHelp/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./DatabaseHelp";
+export * from "./DatabaseHelp";
diff --git a/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.tsx b/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.tsx
index b7790767e09036583e2bbccb0d7755bf2558e9fa..2469370e46eafb87f3215872f73ead84540bf186 100644
--- a/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.tsx
+++ b/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.tsx
@@ -1,60 +1,72 @@
-import React, { useCallback } from "react";
+import React from "react";
 import { t } from "ttag";
 import { updateIn } from "icepick";
+import { useDispatch, useSelector } from "metabase/lib/redux";
 import DatabaseForm from "metabase/databases/containers/DatabaseForm";
 import { DatabaseData } from "metabase-types/api";
-import { InviteInfo, UserInfo } from "metabase-types/store";
-import ActiveStep from "../ActiveStep";
-import InactiveStep from "../InvactiveStep";
-import InviteUserForm from "../InviteUserForm";
-import SetupSection from "../SetupSection";
+import { InviteInfo } from "metabase-types/store";
+import {
+  selectStep,
+  skipDatabase,
+  submitDatabase,
+  submitUserInvite,
+  updateDatabaseEngine,
+} from "../../actions";
+import { DATABASE_STEP } from "../../constants";
+import {
+  getDatabase,
+  getDatabaseEngine,
+  getInvite,
+  getIsEmailConfigured,
+  getIsSetupCompleted,
+  getIsStepActive,
+  getIsStepCompleted,
+  getUser,
+} from "../../selectors";
+import { ActiveStep } from "../ActiveStep";
+import { InactiveStep } from "../InvactiveStep";
+import { InviteUserForm } from "../InviteUserForm";
+import { SetupSection } from "../SetupSection";
 import { StepDescription } from "./DatabaseStep.styled";
 
-export interface DatabaseStepProps {
-  user?: UserInfo;
-  database?: DatabaseData;
-  engine?: string;
-  invite?: InviteInfo;
-  isEmailConfigured: boolean;
-  isStepActive: boolean;
-  isStepCompleted: boolean;
-  isSetupCompleted: boolean;
-  onEngineChange: (engine?: string) => void;
-  onStepSelect: () => void;
-  onDatabaseSubmit: (database: DatabaseData) => void;
-  onInviteSubmit: (invite: InviteInfo) => void;
-  onStepCancel: (engine?: string) => void;
-}
-
-const DatabaseStep = ({
-  user,
-  database,
-  engine,
-  invite,
-  isEmailConfigured,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  onEngineChange,
-  onStepSelect,
-  onDatabaseSubmit,
-  onInviteSubmit,
-  onStepCancel,
-}: DatabaseStepProps): JSX.Element => {
-  const handleSubmit = useCallback(
-    async (database: DatabaseData) => {
-      try {
-        await onDatabaseSubmit(database);
-      } catch (error) {
-        throw getSubmitError(error);
-      }
-    },
-    [onDatabaseSubmit],
+export const DatabaseStep = (): JSX.Element => {
+  const user = useSelector(getUser);
+  const database = useSelector(getDatabase);
+  const engine = useSelector(getDatabaseEngine);
+  const invite = useSelector(getInvite);
+  const isEmailConfigured = useSelector(getIsEmailConfigured);
+  const isStepActive = useSelector(state =>
+    getIsStepActive(state, DATABASE_STEP),
+  );
+  const isStepCompleted = useSelector(state =>
+    getIsStepCompleted(state, DATABASE_STEP),
   );
+  const isSetupCompleted = useSelector(getIsSetupCompleted);
+  const dispatch = useDispatch();
+
+  const handleEngineChange = (engine?: string) => {
+    dispatch(updateDatabaseEngine(engine));
+  };
+
+  const handleDatabaseSubmit = async (database: DatabaseData) => {
+    try {
+      await dispatch(submitDatabase(database)).unwrap();
+    } catch (error) {
+      throw getSubmitError(error);
+    }
+  };
 
-  const handleCancel = useCallback(() => {
-    onStepCancel(engine);
-  }, [engine, onStepCancel]);
+  const handleInviteSubmit = (invite: InviteInfo) => {
+    dispatch(submitUserInvite(invite));
+  };
+
+  const handleStepSelect = () => {
+    dispatch(selectStep(DATABASE_STEP));
+  };
+
+  const handleStepCancel = () => {
+    dispatch(skipDatabase(engine));
+  };
 
   if (!isStepActive) {
     return (
@@ -63,7 +75,7 @@ const DatabaseStep = ({
         label={3}
         isStepCompleted={isStepCompleted}
         isSetupCompleted={isSetupCompleted}
-        onStepSelect={onStepSelect}
+        onStepSelect={handleStepSelect}
       />
     );
   }
@@ -79,9 +91,9 @@ const DatabaseStep = ({
       </StepDescription>
       <DatabaseForm
         initialValues={database}
-        onSubmit={handleSubmit}
-        onEngineChange={onEngineChange}
-        onCancel={handleCancel}
+        onSubmit={handleDatabaseSubmit}
+        onEngineChange={handleEngineChange}
+        onCancel={handleStepCancel}
       />
       {isEmailConfigured && (
         <SetupSection
@@ -91,7 +103,7 @@ const DatabaseStep = ({
           <InviteUserForm
             user={user}
             invite={invite}
-            onSubmit={onInviteSubmit}
+            onSubmit={handleInviteSubmit}
           />
         </SetupSection>
       )}
@@ -120,6 +132,3 @@ const getSubmitError = (error: unknown): unknown => {
     details: errors,
   }));
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default DatabaseStep;
diff --git a/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.unit.spec.tsx b/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.unit.spec.tsx
index ed1a00d396f38cebabb24ef0c1f8e563b6fbd2c8..c0648595bf412c32f274d5e8056c5c6eddb9875e 100644
--- a/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/DatabaseStep/DatabaseStep.unit.spec.tsx
@@ -1,58 +1,62 @@
 import React from "react";
-import { render, screen } from "@testing-library/react";
+import { DatabaseData } from "metabase-types/api";
 import { createMockDatabaseData } from "metabase-types/api/mocks";
-import DatabaseStep, { DatabaseStepProps } from "./DatabaseStep";
+import {
+  createMockSettingsState,
+  createMockSetupState,
+  createMockState,
+} from "metabase-types/store/mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { DATABASE_STEP, PREFERENCES_STEP } from "../../constants";
+import { DatabaseStep } from "./DatabaseStep";
+
+interface SetupOpts {
+  step?: number;
+  database?: DatabaseData;
+  isEmailConfigured?: boolean;
+}
+
+const setup = ({
+  step = DATABASE_STEP,
+  database,
+  isEmailConfigured = false,
+}: SetupOpts = {}) => {
+  const state = createMockState({
+    setup: createMockSetupState({
+      step,
+      database,
+    }),
+    settings: createMockSettingsState({
+      "email-configured?": isEmailConfigured,
+    }),
+  });
 
-const ComponentMock = () => <div />;
-jest.mock("metabase/databases/containers/DatabaseForm", () => ComponentMock);
+  renderWithProviders(<DatabaseStep />, { storeInitialState: state });
+};
 
 describe("DatabaseStep", () => {
   it("should render in active state", () => {
-    const props = getProps({
-      isStepActive: true,
-      isStepCompleted: false,
-    });
-
-    render(<DatabaseStep {...props} />);
+    setup();
 
     expect(screen.getByText("Add your data")).toBeInTheDocument();
   });
 
   it("should render in completed state", () => {
-    const props = getProps({
+    setup({
+      step: PREFERENCES_STEP,
       database: createMockDatabaseData({ name: "Test" }),
-      isStepActive: false,
-      isStepCompleted: true,
     });
 
-    render(<DatabaseStep {...props} />);
-
     expect(screen.getByText("Connecting to Test")).toBeInTheDocument();
   });
 
   it("should render a user invite form", () => {
-    const props = getProps({
-      isStepActive: true,
+    setup({
       isEmailConfigured: true,
     });
 
-    render(<DatabaseStep {...props} />);
-
     expect(
       screen.getByText("Need help connecting to your data?"),
     ).toBeInTheDocument();
   });
 });
-
-const getProps = (opts?: Partial<DatabaseStepProps>): DatabaseStepProps => ({
-  isEmailConfigured: false,
-  isStepActive: false,
-  isStepCompleted: false,
-  isSetupCompleted: false,
-  onEngineChange: jest.fn(),
-  onStepSelect: jest.fn(),
-  onDatabaseSubmit: jest.fn(),
-  onInviteSubmit: jest.fn(),
-  onStepCancel: jest.fn(),
-  ...opts,
-});
diff --git a/frontend/src/metabase/setup/components/DatabaseStep/index.ts b/frontend/src/metabase/setup/components/DatabaseStep/index.ts
index 60865dfe04ad5602e842886023e91522a0e85325..081ac13a7b774de5894e19320a9992bf8c6b5ad5 100644
--- a/frontend/src/metabase/setup/components/DatabaseStep/index.ts
+++ b/frontend/src/metabase/setup/components/DatabaseStep/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./DatabaseStep";
+export * from "./DatabaseStep";
diff --git a/frontend/src/metabase/setup/components/InvactiveStep/InactiveStep.tsx b/frontend/src/metabase/setup/components/InvactiveStep/InactiveStep.tsx
index 8f99c7720d7f7a84519f5d3cc1f6e492462c21c8..ecbdb12d5de0555d8491994bbdcb95fe573b9e15 100644
--- a/frontend/src/metabase/setup/components/InvactiveStep/InactiveStep.tsx
+++ b/frontend/src/metabase/setup/components/InvactiveStep/InactiveStep.tsx
@@ -7,7 +7,7 @@ import {
   StepLabelText,
 } from "./InactiveStep.styled";
 
-export interface InactiveStepProps {
+interface InactiveStepProps {
   title: string;
   label: number;
   isStepCompleted: boolean;
@@ -15,7 +15,7 @@ export interface InactiveStepProps {
   onStepSelect: () => void;
 }
 
-const InactiveStep = ({
+export const InactiveStep = ({
   title,
   label,
   isStepCompleted,
@@ -38,6 +38,3 @@ const InactiveStep = ({
     </StepRoot>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default InactiveStep;
diff --git a/frontend/src/metabase/setup/components/InvactiveStep/index.ts b/frontend/src/metabase/setup/components/InvactiveStep/index.ts
index 291605f2210a473906b60d7740add0669fa4fd42..322c2235fe8e9c969e43cf1fa789690131cace83 100644
--- a/frontend/src/metabase/setup/components/InvactiveStep/index.ts
+++ b/frontend/src/metabase/setup/components/InvactiveStep/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./InactiveStep";
+export * from "./InactiveStep";
diff --git a/frontend/src/metabase/setup/components/InviteUserForm/InviteUserForm.tsx b/frontend/src/metabase/setup/components/InviteUserForm/InviteUserForm.tsx
index c294b2002a8185f465c398881a7f393d9e3be77e..18172b8e9b76ac1b314c312bf9c8e9215d6219a8 100644
--- a/frontend/src/metabase/setup/components/InviteUserForm/InviteUserForm.tsx
+++ b/frontend/src/metabase/setup/components/InviteUserForm/InviteUserForm.tsx
@@ -28,7 +28,7 @@ interface InviteUserFormProps {
   onSubmit: (invite: InviteInfo) => void;
 }
 
-const InviteUserForm = ({
+export const InviteUserForm = ({
   user,
   invite,
   onSubmit,
@@ -70,6 +70,3 @@ const InviteUserForm = ({
     </FormProvider>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default InviteUserForm;
diff --git a/frontend/src/metabase/setup/components/InviteUserForm/index.ts b/frontend/src/metabase/setup/components/InviteUserForm/index.ts
index 25c73a87eb18b8cf28cb57a0d9a36f6858edc49e..e39e131da1cb01091122218bbe1cb29b2a5b70ca 100644
--- a/frontend/src/metabase/setup/components/InviteUserForm/index.ts
+++ b/frontend/src/metabase/setup/components/InviteUserForm/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./InviteUserForm";
+export * from "./InviteUserForm";
diff --git a/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.tsx b/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.tsx
index 934644edafb910894b6796445b3a862ecbe7bd45..c9de70d1623982185c8ecc67de6e746553775dad 100644
--- a/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.tsx
+++ b/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.tsx
@@ -1,12 +1,21 @@
 import React, { useCallback, useMemo } from "react";
 import { t } from "ttag";
 import _ from "underscore";
+import { useDispatch, useSelector } from "metabase/lib/redux";
 import Button from "metabase/core/components/Button";
-import { LocaleData } from "metabase-types/api";
 import { Locale } from "metabase-types/store";
-import ActiveStep from "../ActiveStep";
-import InactiveStep from "../InvactiveStep";
+import { selectStep, updateLocale } from "../../actions";
+import { LANGUAGE_STEP, USER_STEP } from "../../constants";
+import {
+  getAvailableLocales,
+  getIsSetupCompleted,
+  getIsStepActive,
+  getIsStepCompleted,
+  getLocale,
+} from "../../selectors";
 import { getLocales } from "../../utils";
+import { ActiveStep } from "../ActiveStep";
+import { InactiveStep } from "../InvactiveStep";
 import {
   LocaleGroup,
   LocaleInput,
@@ -15,29 +24,31 @@ import {
   StepDescription,
 } from "./LanguageStep.styled";
 
-export interface LanguageStepProps {
-  locale?: Locale;
-  localeData?: LocaleData[];
-  isStepActive: boolean;
-  isStepCompleted: boolean;
-  isSetupCompleted: boolean;
-  onLocaleChange: (locale: Locale) => void;
-  onStepSelect: () => void;
-  onStepSubmit: () => void;
-}
-
-const LanguageStep = ({
-  locale,
-  localeData,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  onLocaleChange,
-  onStepSelect,
-  onStepSubmit,
-}: LanguageStepProps): JSX.Element => {
+export const LanguageStep = (): JSX.Element => {
+  const locale = useSelector(getLocale);
+  const localeData = useSelector(getAvailableLocales);
+  const isStepActive = useSelector(state =>
+    getIsStepActive(state, LANGUAGE_STEP),
+  );
+  const isStepCompleted = useSelector(state =>
+    getIsStepCompleted(state, LANGUAGE_STEP),
+  );
+  const isSetupCompleted = useSelector(state => getIsSetupCompleted(state));
   const fieldId = useMemo(() => _.uniqueId(), []);
   const locales = useMemo(() => getLocales(localeData), [localeData]);
+  const dispatch = useDispatch();
+
+  const handleLocaleChange = (locale: Locale) => {
+    dispatch(updateLocale(locale));
+  };
+
+  const handleStepSelect = () => {
+    dispatch(selectStep(LANGUAGE_STEP));
+  };
+
+  const handleStepSubmit = () => {
+    dispatch(selectStep(USER_STEP));
+  };
 
   if (!isStepActive) {
     return (
@@ -46,7 +57,7 @@ const LanguageStep = ({
         label={1}
         isStepCompleted={isStepCompleted}
         isSetupCompleted={isSetupCompleted}
-        onStepSelect={onStepSelect}
+        onStepSelect={handleStepSelect}
       />
     );
   }
@@ -63,15 +74,17 @@ const LanguageStep = ({
             locale={item}
             checked={item.code === locale?.code}
             fieldId={fieldId}
-            onLocaleChange={onLocaleChange}
+            onLocaleChange={handleLocaleChange}
           />
         ))}
       </LocaleGroup>
       <Button
         primary={locale != null}
         disabled={locale == null}
-        onClick={onStepSubmit}
-      >{t`Next`}</Button>
+        onClick={handleStepSubmit}
+      >
+        {t`Next`}
+      </Button>
     </ActiveStep>
   );
 };
@@ -107,6 +120,3 @@ const LocaleItem = ({
     </LocaleLabel>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default LanguageStep;
diff --git a/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.unit.spec.tsx b/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.unit.spec.tsx
index c53a2019af131947e76083cc2ed03e62d858eebe..4207cc9333147dd2baaecfc260fc5df651583af5 100644
--- a/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/LanguageStep/LanguageStep.unit.spec.tsx
@@ -1,46 +1,53 @@
 import React from "react";
-import { render, screen } from "@testing-library/react";
 import userEvent from "@testing-library/user-event";
 import { Locale } from "metabase-types/store";
-import LanguageStep, { LanguageStepProps } from "./LanguageStep";
+import {
+  createMockLocale,
+  createMockSettingsState,
+  createMockSetupState,
+  createMockState,
+} from "metabase-types/store/mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { LANGUAGE_STEP, USER_STEP } from "../../constants";
+import { LanguageStep } from "./LanguageStep";
+
+interface SetupOpts {
+  step?: number;
+  locale?: Locale;
+}
+
+const setup = ({ step = LANGUAGE_STEP, locale }: SetupOpts = {}) => {
+  const state = createMockState({
+    setup: createMockSetupState({
+      step,
+      locale,
+    }),
+    settings: createMockSettingsState({
+      "available-locales": [["en", "English"]],
+    }),
+  });
+
+  renderWithProviders(<LanguageStep />, { storeInitialState: state });
+};
 
 describe("LanguageStep", () => {
   it("should render in inactive state", () => {
-    const props = getProps({
-      locale: getLocale({ name: "English" }),
-      isStepActive: false,
+    setup({
+      step: USER_STEP,
+      locale: createMockLocale({ name: "English" }),
     });
 
-    render(<LanguageStep {...props} />);
-
     expect(screen.getByText(/set to English/)).toBeInTheDocument();
   });
 
   it("should allow language selection", () => {
-    const props = getProps({
-      isStepActive: true,
-      onLocaleChange: jest.fn(),
+    setup({
+      step: LANGUAGE_STEP,
     });
 
-    render(<LanguageStep {...props} />);
-    userEvent.click(screen.getByText("English"));
+    const option = screen.getByRole("radio", { name: "English" });
+    userEvent.click(option);
 
-    expect(props.onLocaleChange).toHaveBeenCalled();
+    expect(option).toBeChecked();
   });
 });
-
-const getProps = (opts?: Partial<LanguageStepProps>): LanguageStepProps => ({
-  isStepActive: false,
-  isSetupCompleted: false,
-  isStepCompleted: false,
-  onLocaleChange: jest.fn(),
-  onStepSelect: jest.fn(),
-  onStepSubmit: jest.fn(),
-  ...opts,
-});
-
-const getLocale = (opts?: Partial<Locale>): Locale => ({
-  code: "en",
-  name: "English",
-  ...opts,
-});
diff --git a/frontend/src/metabase/setup/components/LanguageStep/index.ts b/frontend/src/metabase/setup/components/LanguageStep/index.ts
index 7529f054067afefda7fed0fb4ab5bda6cf350558..1dc8e31da932f9c688cc3d40c13a02035a8723e5 100644
--- a/frontend/src/metabase/setup/components/LanguageStep/index.ts
+++ b/frontend/src/metabase/setup/components/LanguageStep/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./LanguageStep";
+export * from "./LanguageStep";
diff --git a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx
index 7d80f9bf5c725b6a6275a96af26d2bf155d6f6b6..b2dbebfd7ff05514e6fac32defd5b40597673d24 100644
--- a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx
+++ b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.tsx
@@ -1,10 +1,13 @@
 import React, { useCallback, useState } from "react";
 import { t } from "ttag";
 import * as Yup from "yup";
+import { useSelector } from "metabase/lib/redux";
 import FormProvider from "metabase/core/components/FormProvider";
 import FormSubmitButton from "metabase/core/components/FormSubmitButton";
 import * as Errors from "metabase/core/utils/errors";
 import { SubscribeInfo } from "metabase-types/store";
+import { subscribeToNewsletter } from "../../utils";
+import { getUserEmail } from "../../selectors";
 import {
   EmailForm,
   EmailFormHeader,
@@ -23,25 +26,15 @@ const NEWSLETTER_SCHEMA = Yup.object({
   email: Yup.string().required(Errors.required).email(Errors.email),
 });
 
-export interface NewsletterFormProps {
-  initialEmail?: string;
-  onSubscribe: (email: string) => void;
-}
-
-const NewsletterForm = ({
-  initialEmail = "",
-  onSubscribe,
-}: NewsletterFormProps): JSX.Element => {
-  const initialValues = { email: initialEmail };
+export const NewsletterForm = (): JSX.Element => {
+  const initialEmail = useSelector(getUserEmail);
+  const initialValues = { email: initialEmail ?? "" };
   const [isSubscribed, setIsSubscribed] = useState(false);
 
-  const handleSubmit = useCallback(
-    async ({ email }: SubscribeInfo) => {
-      await onSubscribe(email);
-      setIsSubscribed(true);
-    },
-    [onSubscribe],
-  );
+  const handleSubmit = useCallback(async ({ email }: SubscribeInfo) => {
+    await subscribeToNewsletter(email);
+    setIsSubscribed(true);
+  }, []);
 
   return (
     <EmailFormRoot>
@@ -82,6 +75,3 @@ const NewsletterForm = ({
     </EmailFormRoot>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default NewsletterForm;
diff --git a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx
index f1710c807d03a44038108aa76b286ac14497b9c7..1fc2a955c2b4bed542757a78b2158f1e87c2069f 100644
--- a/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/NewsletterForm/NewsletterForm.unit.spec.tsx
@@ -1,19 +1,37 @@
 import React from "react";
-import { render, screen, waitFor } from "@testing-library/react";
+import fetchMock from "fetch-mock";
 import userEvent from "@testing-library/user-event";
-import NewsletterForm from "./NewsletterForm";
+import {
+  createMockSetupState,
+  createMockState,
+  createMockUserInfo,
+} from "metabase-types/store/mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { SUBSCRIBE_URL } from "../../constants";
+import { NewsletterForm } from "./NewsletterForm";
+
+const USER_EMAIL = "user@metabase.test";
+
+const setup = () => {
+  const state = createMockState({
+    setup: createMockSetupState({
+      user: createMockUserInfo({
+        email: USER_EMAIL,
+      }),
+    }),
+  });
+
+  fetchMock.post(SUBSCRIBE_URL, {});
+  renderWithProviders(<NewsletterForm />, { storeInitialState: state });
+};
 
 describe("NewsletterForm", () => {
   it("should allow to submit the form with the provided email", async () => {
-    const email = "user@metabase.test";
-    const onSubscribe = jest.fn().mockResolvedValue({});
+    setup();
+    expect(screen.getByDisplayValue(USER_EMAIL)).toBeInTheDocument();
 
-    render(<NewsletterForm initialEmail={email} onSubscribe={onSubscribe} />);
     userEvent.click(screen.getByText("Subscribe"));
-
-    await waitFor(() => {
-      expect(onSubscribe).toHaveBeenCalledWith(email);
-    });
-    expect(screen.getByText(/You're subscribed/)).toBeInTheDocument();
+    expect(await screen.findByText(/You're subscribed/)).toBeInTheDocument();
+    expect(fetchMock.done(SUBSCRIBE_URL)).toBe(true);
   });
 });
diff --git a/frontend/src/metabase/setup/components/NewsletterForm/index.ts b/frontend/src/metabase/setup/components/NewsletterForm/index.ts
index f67211d1bc2ddff3b795a366b7fa1317003ffbb2..72401a4e8feec3d93490e74114daf26b494d766a 100644
--- a/frontend/src/metabase/setup/components/NewsletterForm/index.ts
+++ b/frontend/src/metabase/setup/components/NewsletterForm/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./NewsletterForm";
+export * from "./NewsletterForm";
diff --git a/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.tsx b/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.tsx
index 896544afee6da25ed7daffe9efae7cf5c775fa6a..22964ad75596d7f350de4a1212d7ccc71168de68 100644
--- a/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.tsx
+++ b/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.tsx
@@ -1,11 +1,20 @@
 import React, { useState } from "react";
 import { t, jt } from "ttag";
 import { getIn } from "icepick";
+import { useDispatch, useSelector } from "metabase/lib/redux";
 import Settings from "metabase/lib/settings";
 import ActionButton from "metabase/components/ActionButton";
 import ExternalLink from "metabase/core/components/ExternalLink";
-import ActiveStep from "../ActiveStep";
-import InactiveStep from "../InvactiveStep";
+import { selectStep, submitSetup, updateTracking } from "../../actions";
+import { PREFERENCES_STEP } from "../../constants";
+import {
+  getIsSetupCompleted,
+  getIsStepActive,
+  getIsStepCompleted,
+  getIsTrackingAllowed,
+} from "../../selectors";
+import { ActiveStep } from "../ActiveStep";
+import { InactiveStep } from "../InvactiveStep";
 import {
   StepDescription,
   StepToggleContainer,
@@ -15,30 +24,29 @@ import {
   StepToggle,
 } from "./PreferencesStep.styled";
 
-export interface PreferencesStepProps {
-  isTrackingAllowed: boolean;
-  isStepActive: boolean;
-  isStepCompleted: boolean;
-  isSetupCompleted: boolean;
-  onTrackingChange: (isTrackingAllowed: boolean) => void;
-  onStepSelect: () => void;
-  onStepSubmit: (isTrackingAllowed: boolean) => void;
-}
-
-const PreferencesStep = ({
-  isTrackingAllowed,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  onTrackingChange,
-  onStepSelect,
-  onStepSubmit,
-}: PreferencesStepProps): JSX.Element => {
+export const PreferencesStep = (): JSX.Element => {
   const [errorMessage, setErrorMessage] = useState<string>();
+  const isTrackingAllowed = useSelector(getIsTrackingAllowed);
+  const isStepActive = useSelector(state =>
+    getIsStepActive(state, PREFERENCES_STEP),
+  );
+  const isStepCompleted = useSelector(state =>
+    getIsStepCompleted(state, PREFERENCES_STEP),
+  );
+  const isSetupCompleted = useSelector(getIsSetupCompleted);
+  const dispatch = useDispatch();
 
-  const handleSubmit = async () => {
+  const handleTrackingChange = (isTrackingAllowed: boolean) => {
+    dispatch(updateTracking(isTrackingAllowed));
+  };
+
+  const handleStepSelect = () => {
+    dispatch(selectStep(PREFERENCES_STEP));
+  };
+
+  const handleStepSubmit = async () => {
     try {
-      await onStepSubmit(isTrackingAllowed);
+      await dispatch(submitSetup()).unwrap();
     } catch (error) {
       setErrorMessage(getSubmitError(error));
       throw error;
@@ -52,7 +60,7 @@ const PreferencesStep = ({
         label={4}
         isStepCompleted={isStepCompleted}
         isSetupCompleted={isSetupCompleted}
-        onStepSelect={onStepSelect}
+        onStepSelect={handleStepSelect}
       />
     );
   }
@@ -74,7 +82,7 @@ const PreferencesStep = ({
         <StepToggle
           value={isTrackingAllowed}
           autoFocus
-          onChange={onTrackingChange}
+          onChange={handleTrackingChange}
           aria-labelledby="anonymous-usage-events-label"
         />
         <StepToggleLabel id="anonymous-usage-events-label">
@@ -97,7 +105,7 @@ const PreferencesStep = ({
         successText={t`Success`}
         primary
         type="button"
-        actionFn={handleSubmit}
+        actionFn={handleStepSubmit}
       />
       {errorMessage && <StepError>{errorMessage}</StepError>}
     </ActiveStep>
@@ -129,6 +137,3 @@ const getSubmitError = (error: unknown): string => {
     return t`An error occurred`;
   }
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default PreferencesStep;
diff --git a/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.unit.spec.tsx b/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.unit.spec.tsx
index fb2cfd3e155736f08bcf8762024e723d1d5d2efa..4d1e809246fe24a9de0df782dcc596a12c60afd0 100644
--- a/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/PreferencesStep/PreferencesStep.unit.spec.tsx
@@ -1,54 +1,50 @@
 import React from "react";
-import { render, screen } from "@testing-library/react";
 import userEvent from "@testing-library/user-event";
-import PreferencesStep, { PreferencesStepProps } from "./PreferencesStep";
+import {
+  createMockSetupState,
+  createMockState,
+} from "metabase-types/store/mocks";
+import { setupErrorSetupEndpoints } from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { PREFERENCES_STEP, USER_STEP } from "../../constants";
+import { PreferencesStep } from "./PreferencesStep";
+
+interface SetupOpts {
+  step?: number;
+}
+
+const setup = ({ step = PREFERENCES_STEP }: SetupOpts = {}) => {
+  const state = createMockState({
+    setup: createMockSetupState({
+      step,
+    }),
+  });
+
+  setupErrorSetupEndpoints();
+  renderWithProviders(<PreferencesStep />, { storeInitialState: state });
+};
 
 describe("PreferencesStep", () => {
   it("should render in inactive state", () => {
-    const props = getProps({
-      isStepActive: false,
-    });
-
-    render(<PreferencesStep {...props} />);
+    setup({ step: USER_STEP });
 
     expect(screen.getByText("Usage data preferences")).toBeInTheDocument();
   });
 
   it("should allow toggling tracking permissions", () => {
-    const props = getProps({
-      isTrackingAllowed: false,
-      isStepActive: true,
-    });
+    setup({ step: PREFERENCES_STEP });
 
-    render(<PreferencesStep {...props} />);
-    userEvent.click(screen.getByLabelText(/Allow Metabase/));
+    const toggle = screen.getByRole("switch", { name: /Allow Metabase/ });
+    userEvent.click(toggle);
 
-    expect(props.onTrackingChange).toHaveBeenCalledWith(true);
+    expect(toggle).toBeChecked();
   });
 
   it("should show an error message on submit", async () => {
-    const props = getProps({
-      isTrackingAllowed: true,
-      isStepActive: true,
-      onStepSubmit: jest.fn().mockRejectedValue({}),
-    });
+    setup({ step: PREFERENCES_STEP });
 
-    render(<PreferencesStep {...props} />);
     userEvent.click(screen.getByText("Finish"));
 
     expect(await screen.findByText("An error occurred")).toBeInTheDocument();
   });
 });
-
-const getProps = (
-  opts?: Partial<PreferencesStepProps>,
-): PreferencesStepProps => ({
-  isTrackingAllowed: false,
-  isStepActive: false,
-  isStepCompleted: false,
-  isSetupCompleted: false,
-  onTrackingChange: jest.fn(),
-  onStepSelect: jest.fn(),
-  onStepSubmit: jest.fn(),
-  ...opts,
-});
diff --git a/frontend/src/metabase/setup/components/PreferencesStep/index.ts b/frontend/src/metabase/setup/components/PreferencesStep/index.ts
index 245a6767d0b888140dd23664a89130449600b8b7..424eca37900514ba489cf25063b38f7dda33b340 100644
--- a/frontend/src/metabase/setup/components/PreferencesStep/index.ts
+++ b/frontend/src/metabase/setup/components/PreferencesStep/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./PreferencesStep";
+export * from "./PreferencesStep";
diff --git a/frontend/src/metabase/setup/components/SettingsPage/SettingsPage.tsx b/frontend/src/metabase/setup/components/SettingsPage/SettingsPage.tsx
index bbc2c6af1b105c54f778c16d14cd31a55a7730f0..75fb1857b5d1bb0ed6d8a420cbf5e1b5b343b976 100644
--- a/frontend/src/metabase/setup/components/SettingsPage/SettingsPage.tsx
+++ b/frontend/src/metabase/setup/components/SettingsPage/SettingsPage.tsx
@@ -1,47 +1,31 @@
-import React, { useEffect } from "react";
+import React from "react";
 import LogoIcon from "metabase/components/LogoIcon";
-import MigrationHelp from "metabase/setup/containers/CloudMigrationHelp";
-import LanguageStep from "../../containers/LanguageStep";
-import UserStep from "../../containers/UserStep";
-import DatabaseStep from "../../containers/DatabaseStep";
-import DatabaseHelp from "../../containers/DatabaseHelp";
-import PreferencesStep from "../../containers/PreferencesStep";
-import CompletedStep from "../../containers/CompletedStep";
-import SetupHelp from "../SetupHelp";
-import { PageHeader, PageBody } from "./SettingsPage.styled";
-
-export interface SettingsPageProps {
-  step: number;
-  onStepShow: (step: number) => void;
-}
-
-const SettingsPage = ({
-  step,
-  onStepShow,
-  ...props
-}: SettingsPageProps): JSX.Element => {
-  useEffect(() => {
-    onStepShow(step);
-  }, [step, onStepShow]);
+import { CloudMigrationHelp } from "../CloudMigrationHelp";
+import { DatabaseHelp } from "../DatabaseHelp";
+import { DatabaseStep } from "../DatabaseStep";
+import { CompletedStep } from "../CompletedStep";
+import { LanguageStep } from "../LanguageStep";
+import { PreferencesStep } from "../PreferencesStep";
+import { UserStep } from "../UserStep";
+import { SetupHelp } from "../SetupHelp";
+import { PageBody, PageHeader } from "./SettingsPage.styled";
 
+export const SettingsPage = (): JSX.Element => {
   return (
     <div>
       <PageHeader>
         <LogoIcon height={51} />
       </PageHeader>
       <PageBody>
-        <LanguageStep {...props} />
-        <UserStep {...props} />
-        <DatabaseStep {...props} />
-        <DatabaseHelp {...props} />
-        <PreferencesStep {...props} />
-        <CompletedStep {...props} />
-        <MigrationHelp {...props} />
-        <SetupHelp {...props} />
+        <LanguageStep />
+        <UserStep />
+        <DatabaseStep />
+        <DatabaseHelp />
+        <PreferencesStep />
+        <CompletedStep />
+        <CloudMigrationHelp />
+        <SetupHelp />
       </PageBody>
     </div>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default SettingsPage;
diff --git a/frontend/src/metabase/setup/components/SettingsPage/index.ts b/frontend/src/metabase/setup/components/SettingsPage/index.ts
index 4504f4dc20bd05f2d355936ed42d7bb43a0b8177..ac38eafcaf2dd1ff22c576eae522cf67a8570ced 100644
--- a/frontend/src/metabase/setup/components/SettingsPage/index.ts
+++ b/frontend/src/metabase/setup/components/SettingsPage/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./SettingsPage";
+export * from "./SettingsPage";
diff --git a/frontend/src/metabase/setup/components/Setup/Setup.tsx b/frontend/src/metabase/setup/components/Setup/Setup.tsx
index 9bf95fa0bcf7c22f2ea6634e897dc138c4bb3560..7491b5a3241e40acc6b18e5c9da7511c0c475a29 100644
--- a/frontend/src/metabase/setup/components/Setup/Setup.tsx
+++ b/frontend/src/metabase/setup/components/Setup/Setup.tsx
@@ -1,18 +1,30 @@
-import React from "react";
-import SettingsPage from "../../containers/SettingsPage";
-import WelcomePage from "../../containers/WelcomePage";
+import React, { useEffect } from "react";
+import { useUpdate } from "react-use";
+import { useSelector } from "metabase/lib/redux";
+import { trackStepSeen } from "../../analytics";
+import { WELCOME_STEP } from "../../constants";
+import { getIsLocaleLoaded, getStep } from "../../selectors";
+import { SettingsPage } from "../SettingsPage";
+import { WelcomePage } from "../WelcomePage";
 
-export interface SetupProps {
-  isWelcome: boolean;
-}
+export const Setup = (): JSX.Element => {
+  const step = useSelector(getStep);
+  const isLocaleLoaded = useSelector(getIsLocaleLoaded);
+  const update = useUpdate();
 
-const Setup = ({ isWelcome, ...props }: SetupProps): JSX.Element => {
-  if (isWelcome) {
-    return <WelcomePage {...props} />;
+  useEffect(() => {
+    trackStepSeen(step);
+  }, [step]);
+
+  useEffect(() => {
+    if (isLocaleLoaded) {
+      update();
+    }
+  }, [update, isLocaleLoaded]);
+
+  if (step === WELCOME_STEP) {
+    return <WelcomePage />;
   } else {
-    return <SettingsPage {...props} />;
+    return <SettingsPage />;
   }
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default Setup;
diff --git a/frontend/src/metabase/setup/components/Setup/index.ts b/frontend/src/metabase/setup/components/Setup/index.ts
index e5707f00203a679f48142c3a4fbc55d2d1ee1063..b68d7bc2a4dddad8bd9f23b9603de53253b18048 100644
--- a/frontend/src/metabase/setup/components/Setup/index.ts
+++ b/frontend/src/metabase/setup/components/Setup/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./Setup";
+export * from "./Setup";
diff --git a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
index 00ef36fdab73d4516c868c680f6d31b34a40c921..1e9f244b1dfc182dfa720cb76192724f9036e715 100644
--- a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
+++ b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx
@@ -4,7 +4,7 @@ import MetabaseSettings from "metabase/lib/settings";
 import ExternalLink from "metabase/core/components/ExternalLink";
 import { SetupFooterRoot } from "./SetupHelp.styled";
 
-const SetupHelp = (): JSX.Element => {
+export const SetupHelp = (): JSX.Element => {
   return (
     <SetupFooterRoot>
       {t`If you feel stuck`},{" "}
@@ -19,6 +19,3 @@ const SetupHelp = (): JSX.Element => {
     </SetupFooterRoot>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default SetupHelp;
diff --git a/frontend/src/metabase/setup/components/SetupHelp/index.ts b/frontend/src/metabase/setup/components/SetupHelp/index.ts
index ffc0bb4464617b859706c63eb80778cefa6e9fbe..314044fa871861ac9b2ebf3d8bfe82c23000ceb3 100644
--- a/frontend/src/metabase/setup/components/SetupHelp/index.ts
+++ b/frontend/src/metabase/setup/components/SetupHelp/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./SetupHelp";
+export * from "./SetupHelp";
diff --git a/frontend/src/metabase/setup/components/SetupSection/SetupSection.tsx b/frontend/src/metabase/setup/components/SetupSection/SetupSection.tsx
index 71fc7c7ff301dcf14ac755e1658c251d9b0d4261..074a62d5a02d4bc3c8ff9a7e009a890a777ae20f 100644
--- a/frontend/src/metabase/setup/components/SetupSection/SetupSection.tsx
+++ b/frontend/src/metabase/setup/components/SetupSection/SetupSection.tsx
@@ -9,13 +9,13 @@ import {
   SectionButton,
 } from "./SetupSection.styled";
 
-export interface SetupSectionProps {
+interface SetupSectionProps {
   title: ReactNode;
   description?: ReactNode;
   children?: ReactNode;
 }
 
-const SetupSection = ({
+export const SetupSection = ({
   title,
   description,
   children,
@@ -41,6 +41,3 @@ const SetupSection = ({
     </SectionRoot>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default SetupSection;
diff --git a/frontend/src/metabase/setup/components/SetupSection/index.ts b/frontend/src/metabase/setup/components/SetupSection/index.ts
index 4e4f7a11c9f1d70e0595b938b45d6344e055614f..aa90fcb403403414f4b4fc20e54e52768e159980 100644
--- a/frontend/src/metabase/setup/components/SetupSection/index.ts
+++ b/frontend/src/metabase/setup/components/SetupSection/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./SetupSection";
+export * from "./SetupSection";
diff --git a/frontend/src/metabase/setup/components/UserForm/UserForm.tsx b/frontend/src/metabase/setup/components/UserForm/UserForm.tsx
index 1f363bc6c59cb2b3b092a3e9302c2be66624fe05..37e43980eed6714b55e5c3e0fbe6b07175e7dc28 100644
--- a/frontend/src/metabase/setup/components/UserForm/UserForm.tsx
+++ b/frontend/src/metabase/setup/components/UserForm/UserForm.tsx
@@ -33,7 +33,11 @@ interface UserFormProps {
   onSubmit: (user: UserInfo) => void;
 }
 
-const UserForm = ({ user, onValidatePassword, onSubmit }: UserFormProps) => {
+export const UserForm = ({
+  user,
+  onValidatePassword,
+  onSubmit,
+}: UserFormProps) => {
   const initialValues = useMemo(() => {
     return user ?? USER_SCHEMA.getDefault();
   }, [user]);
@@ -96,6 +100,3 @@ const UserForm = ({ user, onValidatePassword, onSubmit }: UserFormProps) => {
     </FormProvider>
   );
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default UserForm;
diff --git a/frontend/src/metabase/setup/components/UserForm/index.ts b/frontend/src/metabase/setup/components/UserForm/index.ts
index f572033248f30bcfb9e23b0b18b8b5cddae0d811..543de93ad1f66f360e2886ebaf9b5faf7a0d5415 100644
--- a/frontend/src/metabase/setup/components/UserForm/index.ts
+++ b/frontend/src/metabase/setup/components/UserForm/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./UserForm";
+export * from "./UserForm";
diff --git a/frontend/src/metabase/setup/components/UserStep/UserStep.tsx b/frontend/src/metabase/setup/components/UserStep/UserStep.tsx
index 091ab252f366b080da26755550c4200e23826dc6..f8e311291ea46bf0bd226c874c528c0ab81b7f79 100644
--- a/frontend/src/metabase/setup/components/UserStep/UserStep.tsx
+++ b/frontend/src/metabase/setup/components/UserStep/UserStep.tsx
@@ -1,32 +1,40 @@
 import React from "react";
 import { t } from "ttag";
+import { useDispatch, useSelector } from "metabase/lib/redux";
 import { UserInfo } from "metabase-types/store";
-import ActiveStep from "../ActiveStep";
-import InactiveStep from "../InvactiveStep";
-import UserForm from "../UserForm";
+import { ActiveStep } from "../ActiveStep";
+import { InactiveStep } from "../InvactiveStep";
+import { UserForm } from "../UserForm";
+import { selectStep, submitUser } from "../../actions";
+import { USER_STEP } from "../../constants";
+import {
+  getIsHosted,
+  getIsSetupCompleted,
+  getIsStepActive,
+  getIsStepCompleted,
+  getUser,
+} from "../../selectors";
+import { validatePassword } from "../../utils";
 import { StepDescription } from "./UserStep.styled";
 
-export interface UserStepProps {
-  user?: UserInfo;
-  isHosted: boolean;
-  isStepActive: boolean;
-  isStepCompleted: boolean;
-  isSetupCompleted: boolean;
-  onValidatePassword: (password: string) => Promise<string | undefined>;
-  onStepSelect: () => void;
-  onStepSubmit: (user: UserInfo) => void;
-}
+export const UserStep = (): JSX.Element => {
+  const user = useSelector(getUser);
+  const isHosted = useSelector(getIsHosted);
+  const isStepActive = useSelector(state => getIsStepActive(state, USER_STEP));
+  const isStepCompleted = useSelector(state =>
+    getIsStepCompleted(state, USER_STEP),
+  );
+  const isSetupCompleted = useSelector(getIsSetupCompleted);
+  const dispatch = useDispatch();
+
+  const handleStepSelect = () => {
+    dispatch(selectStep(USER_STEP));
+  };
+
+  const handleSubmit = (user: UserInfo) => {
+    dispatch(submitUser(user));
+  };
 
-const UserStep = ({
-  user,
-  isHosted,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  onValidatePassword,
-  onStepSelect,
-  onStepSubmit,
-}: UserStepProps): JSX.Element => {
   if (!isStepActive) {
     return (
       <InactiveStep
@@ -34,7 +42,7 @@ const UserStep = ({
         label={2}
         isStepCompleted={isStepCompleted}
         isSetupCompleted={isSetupCompleted}
-        onStepSelect={onStepSelect}
+        onStepSelect={handleStepSelect}
       />
     );
   }
@@ -49,8 +57,8 @@ const UserStep = ({
       )}
       <UserForm
         user={user}
-        onValidatePassword={onValidatePassword}
-        onSubmit={onStepSubmit}
+        onValidatePassword={validatePassword}
+        onSubmit={handleSubmit}
       />
     </ActiveStep>
   );
@@ -62,6 +70,3 @@ const getStepTitle = (user: UserInfo | undefined, isStepCompleted: boolean) => {
     ? t`Hi${namePart}. Nice to meet you!`
     : t`What should we call you?`;
 };
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default UserStep;
diff --git a/frontend/src/metabase/setup/components/UserStep/UserStep.unit.spec.tsx b/frontend/src/metabase/setup/components/UserStep/UserStep.unit.spec.tsx
index b7022fe5de00da54b5a7a2c0245f39702dd3f119..3f06df1b2bd2938ced5ef9be67aca74b897cc2fa 100644
--- a/frontend/src/metabase/setup/components/UserStep/UserStep.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/UserStep/UserStep.unit.spec.tsx
@@ -1,50 +1,43 @@
 import React from "react";
-import { render, screen } from "@testing-library/react";
 import { UserInfo } from "metabase-types/store";
-import UserStep, { UserStepProps } from "./UserStep";
+import { renderWithProviders, screen } from "__support__/ui";
+import {
+  createMockSetupState,
+  createMockState,
+  createMockUserInfo,
+} from "metabase-types/store/mocks";
+import { DATABASE_STEP, USER_STEP } from "../../constants";
+import { UserStep } from "./UserStep";
+
+interface SetupOpts {
+  step?: number;
+  user?: UserInfo;
+}
+
+const setup = ({ step = USER_STEP, user }: SetupOpts = {}) => {
+  const state = createMockState({
+    setup: createMockSetupState({
+      step,
+      user,
+    }),
+  });
+
+  renderWithProviders(<UserStep />, { storeInitialState: state });
+};
 
 describe("UserStep", () => {
   it("should render in active state", () => {
-    const props = getProps({
-      isStepActive: true,
-      isStepCompleted: false,
-    });
-
-    render(<UserStep {...props} />);
+    setup({ step: USER_STEP });
 
     expect(screen.getByText("What should we call you?")).toBeInTheDocument();
   });
 
   it("should render in completed state", () => {
-    const props = getProps({
-      user: getUserInfo({ first_name: "Testy" }),
-      isStepActive: false,
-      isStepCompleted: true,
+    setup({
+      step: DATABASE_STEP,
+      user: createMockUserInfo({ first_name: "Testy" }),
     });
 
-    render(<UserStep {...props} />);
-
     expect(screen.getByText(/Hi, Testy/)).toBeInTheDocument();
   });
 });
-
-const getProps = (opts?: Partial<UserStepProps>): UserStepProps => ({
-  isHosted: false,
-  isStepActive: false,
-  isStepCompleted: false,
-  isSetupCompleted: false,
-  onValidatePassword: jest.fn(),
-  onStepSelect: jest.fn(),
-  onStepSubmit: jest.fn(),
-  ...opts,
-});
-
-const getUserInfo = (opts?: Partial<UserInfo>): UserInfo => ({
-  first_name: "Testy",
-  last_name: "McTestface",
-  email: "testy@metabase.test",
-  site_name: "Epic Team",
-  password: "metasample123",
-  password_confirm: "metasample123",
-  ...opts,
-});
diff --git a/frontend/src/metabase/setup/components/UserStep/index.ts b/frontend/src/metabase/setup/components/UserStep/index.ts
index 1b53c6e796ef87ee9bd4e450381fb9351ba8e6b2..5282afb1448113f00e122a0d92f0a8d468b190fc 100644
--- a/frontend/src/metabase/setup/components/UserStep/index.ts
+++ b/frontend/src/metabase/setup/components/UserStep/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./UserStep";
+export * from "./UserStep";
diff --git a/frontend/src/metabase/setup/components/UserStep/types.ts b/frontend/src/metabase/setup/components/UserStep/types.ts
deleted file mode 100644
index 77ddb0ba99d029e13508bee4cc81bfa6725e12c4..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/components/UserStep/types.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ComponentType } from "react";
-
-export interface FormProps {
-  Form: ComponentType;
-  FormField: ComponentType<FormFieldProps>;
-  FormFooter: ComponentType<FormFooterProps>;
-}
-
-export interface FormFieldProps {
-  name: string;
-}
-
-export interface FormFooterProps {
-  submitTitle?: string;
-}
diff --git a/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.tsx b/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.tsx
index 23271c6b9e0d9b520b0967c58b79baf8a8114f19..74883c0a49583b7bec566e6156d850b4c79ce2ef 100644
--- a/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.tsx
+++ b/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.tsx
@@ -1,8 +1,12 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect } from "react";
+import { useTimeout } from "react-use";
 import { t } from "ttag";
+import { useDispatch, useSelector } from "metabase/lib/redux";
 import LogoIcon from "metabase/components/LogoIcon";
-import { LOCALE_TIMEOUT } from "../../constants";
-import SetupHelp from "../SetupHelp";
+import { loadDefaults, selectStep } from "../../actions";
+import { LANGUAGE_STEP, LOCALE_TIMEOUT } from "../../constants";
+import { getIsLocaleLoaded } from "../../selectors";
+import { SetupHelp } from "../SetupHelp";
 import {
   PageRoot,
   PageMain,
@@ -11,24 +15,20 @@ import {
   PageButton,
 } from "./WelcomePage.styled";
 
-export interface WelcomePageProps {
-  isLocaleLoaded: boolean;
-  onStepShow: () => void;
-  onStepSubmit: () => void;
-}
+export const WelcomePage = (): JSX.Element | null => {
+  const [isElapsed] = useTimeout(LOCALE_TIMEOUT);
+  const isLocaleLoaded = useSelector(getIsLocaleLoaded);
+  const dispatch = useDispatch();
 
-const WelcomePage = ({
-  isLocaleLoaded,
-  onStepShow,
-  onStepSubmit,
-}: WelcomePageProps): JSX.Element | null => {
-  const isElapsed = useIsElapsed(LOCALE_TIMEOUT);
+  const handleStepSubmit = () => {
+    dispatch(selectStep(LANGUAGE_STEP));
+  };
 
   useEffect(() => {
-    onStepShow();
-  }, [onStepShow]);
+    dispatch(loadDefaults());
+  }, [dispatch]);
 
-  if (!isElapsed && !isLocaleLoaded) {
+  if (!isElapsed() && !isLocaleLoaded) {
     return null;
   }
 
@@ -41,27 +41,11 @@ const WelcomePage = ({
           {t`Looks like everything is working.`}{" "}
           {t`Now let’s get to know you, connect to your data, and start finding you some answers!`}
         </PageBody>
-        <PageButton
-          primary
-          autoFocus
-          onClick={onStepSubmit}
-        >{t`Let's get started`}</PageButton>
+        <PageButton primary autoFocus onClick={handleStepSubmit}>
+          {t`Let's get started`}
+        </PageButton>
       </PageMain>
       <SetupHelp />
     </PageRoot>
   );
 };
-
-const useIsElapsed = (delay: number) => {
-  const [isElapsed, setIsElapsed] = useState(false);
-
-  useEffect(() => {
-    const timeout = setTimeout(() => setIsElapsed(true), delay);
-    return () => clearTimeout(timeout);
-  }, [delay]);
-
-  return isElapsed;
-};
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default WelcomePage;
diff --git a/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.unit.spec.tsx b/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.unit.spec.tsx
index f3829c356f8761d37cb92c39acdd43d5d900bfc8..7783520d744fadc019964529163acb4c3f0e582f 100644
--- a/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.unit.spec.tsx
+++ b/frontend/src/metabase/setup/components/WelcomePage/WelcomePage.unit.spec.tsx
@@ -1,49 +1,26 @@
 import React from "react";
-import { render, screen } from "@testing-library/react";
-import WelcomePage, { WelcomePageProps } from "./WelcomePage";
-
-describe("WelcomePage", () => {
-  beforeEach(() => {
-    jest.useFakeTimers();
-  });
-
-  afterEach(() => {
-    jest.useRealTimers();
+import {
+  createMockSettingsState,
+  createMockState,
+} from "metabase-types/store/mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import { WelcomePage } from "./WelcomePage";
+
+const setup = () => {
+  const state = createMockState({
+    settings: createMockSettingsState({
+      "available-locales": [["en", "English"]],
+    }),
   });
 
-  it("should not render until the locale is loaded", () => {
-    const props = getProps({ isLocaleLoaded: false });
+  renderWithProviders(<WelcomePage />, { storeInitialState: state });
+};
 
-    render(<WelcomePage {...props} />);
+describe("WelcomePage", () => {
+  it("should render before the timeout when the locale is loaded", async () => {
+    setup();
 
     expect(screen.queryByText("Welcome to Metabase")).not.toBeInTheDocument();
+    expect(await screen.findByText("Welcome to Metabase")).toBeInTheDocument();
   });
-
-  it("should render after some time even if the locale is not loaded", () => {
-    const oldProps = getProps({ isLocaleLoaded: false });
-    const newProps = getProps({ isLocaleLoaded: false });
-
-    const { rerender } = render(<WelcomePage {...oldProps} />);
-    jest.advanceTimersByTime(310);
-    rerender(<WelcomePage {...newProps} />);
-
-    expect(screen.getByText("Welcome to Metabase")).toBeInTheDocument();
-  });
-
-  it("should render before the timeout if the locale is loaded", () => {
-    const oldProps = getProps({ isLocaleLoaded: false });
-    const newProps = getProps({ isLocaleLoaded: true });
-
-    const { rerender } = render(<WelcomePage {...oldProps} />);
-    rerender(<WelcomePage {...newProps} />);
-
-    expect(screen.getByText("Welcome to Metabase")).toBeInTheDocument();
-  });
-});
-
-const getProps = (opts?: Partial<WelcomePageProps>): WelcomePageProps => ({
-  isLocaleLoaded: false,
-  onStepShow: jest.fn(),
-  onStepSubmit: jest.fn(),
-  ...opts,
 });
diff --git a/frontend/src/metabase/setup/components/WelcomePage/index.ts b/frontend/src/metabase/setup/components/WelcomePage/index.ts
index 5e8fd9ce42fc56d23bd5f3b0cd2ee13e4fa595fb..741f2b83079f5764f1bed18669856deeac2fe0aa 100644
--- a/frontend/src/metabase/setup/components/WelcomePage/index.ts
+++ b/frontend/src/metabase/setup/components/WelcomePage/index.ts
@@ -1,2 +1 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./WelcomePage";
+export * from "./WelcomePage";
diff --git a/frontend/src/metabase/setup/constants.ts b/frontend/src/metabase/setup/constants.ts
index 2b781deee24367f7e2b3a003e500680a625e663e..49eb2fa3ea28c02be3804bb7909b8bafe155c4c4 100644
--- a/frontend/src/metabase/setup/constants.ts
+++ b/frontend/src/metabase/setup/constants.ts
@@ -15,3 +15,7 @@ export const STEPS: Record<number, string> = {
   [PREFERENCES_STEP]: "data_usage",
   [COMPLETED_STEP]: "completed",
 };
+
+export const SUBSCRIBE_URL =
+  "https://metabase.us10.list-manage.com/subscribe/post?u=869fec0e4689e8fd1db91e795&id=b9664113a8";
+export const SUBSCRIBE_TOKEN = "b_869fec0e4689e8fd1db91e795_b9664113a8";
diff --git a/frontend/src/metabase/setup/containers/CloudMigrationHelp/CloudMigrationHelp.tsx b/frontend/src/metabase/setup/containers/CloudMigrationHelp/CloudMigrationHelp.tsx
deleted file mode 100644
index c69366987f0d2ae22847341617d9ca6644f0a7b6..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/CloudMigrationHelp/CloudMigrationHelp.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from "react";
-import { connect } from "react-redux";
-import { t } from "ttag";
-
-import HelpCard from "metabase/components/HelpCard";
-import { SetupCardContainer } from "metabase/setup/components/SetupCardContainer";
-
-import MetabaseSettings from "metabase/lib/settings";
-import { getSetting } from "metabase/selectors/settings";
-
-import type { State } from "metabase-types/store";
-
-import { COMPLETED_STEP } from "../../constants";
-import { isStepActive } from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  isHosted: getSetting(state, "is-hosted?"),
-  isStepActive: isStepActive(state, COMPLETED_STEP),
-});
-
-interface CloudMigrationHelpProps {
-  isHosted: boolean;
-  isStepActive: boolean;
-}
-
-const CloudMigrationHelp = ({
-  isHosted,
-  isStepActive,
-}: CloudMigrationHelpProps) => {
-  const isVisible = isHosted && isStepActive;
-
-  return (
-    <SetupCardContainer isVisible={isVisible}>
-      <HelpCard
-        title={t`Migrating from self-hosted?`}
-        helpUrl={MetabaseSettings.migrateToCloudGuideUrl()}
-      >{t`Check out our docs for how to migrate your self-hosted instance to Cloud.`}</HelpCard>
-    </SetupCardContainer>
-  );
-};
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps)(CloudMigrationHelp);
diff --git a/frontend/src/metabase/setup/containers/CloudMigrationHelp/index.ts b/frontend/src/metabase/setup/containers/CloudMigrationHelp/index.ts
deleted file mode 100644
index 8c4ece7c071325d6e006a29197b5c7b95019a611..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/CloudMigrationHelp/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./CloudMigrationHelp";
diff --git a/frontend/src/metabase/setup/containers/CompletedStep/CompletedStep.tsx b/frontend/src/metabase/setup/containers/CompletedStep/CompletedStep.tsx
deleted file mode 100644
index 52f6d59a3dad2c09d7a745773db4703afe61a21c..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/CompletedStep/CompletedStep.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { connect } from "react-redux";
-import { State } from "metabase-types/store";
-import CompletedStep from "../../components/CompletedStep";
-import { COMPLETED_STEP } from "../../constants";
-import { isLocaleLoaded, isStepActive } from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  isStepActive: isStepActive(state, COMPLETED_STEP),
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps)(CompletedStep);
diff --git a/frontend/src/metabase/setup/containers/CompletedStep/index.ts b/frontend/src/metabase/setup/containers/CompletedStep/index.ts
deleted file mode 100644
index 43cf9c3b966d53ca03603c00a8717aa3bd9c49b8..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/CompletedStep/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./CompletedStep";
diff --git a/frontend/src/metabase/setup/containers/DatabaseHelp/DatabaseHelp.tsx b/frontend/src/metabase/setup/containers/DatabaseHelp/DatabaseHelp.tsx
deleted file mode 100644
index 34fa4f327ea0662f7dc2f2a8e6cf2de20ad34883..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/DatabaseHelp/DatabaseHelp.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { connect } from "react-redux";
-import { State } from "metabase-types/store";
-import DatabaseHelp from "../../components/DatabaseHelp";
-import { DATABASE_STEP } from "../../constants";
-import {
-  getDatabaseEngine,
-  isLocaleLoaded,
-  isStepActive,
-} from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  engine: getDatabaseEngine(state),
-  isStepActive: isStepActive(state, DATABASE_STEP),
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps)(DatabaseHelp);
diff --git a/frontend/src/metabase/setup/containers/DatabaseHelp/index.ts b/frontend/src/metabase/setup/containers/DatabaseHelp/index.ts
deleted file mode 100644
index c82449309469f225e0c47b85b7fe6bfc6f9592b5..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/DatabaseHelp/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./DatabaseHelp";
diff --git a/frontend/src/metabase/setup/containers/DatabaseStep/DatabaseStep.tsx b/frontend/src/metabase/setup/containers/DatabaseStep/DatabaseStep.tsx
deleted file mode 100644
index 9a830af4e1684d4803921389dfa04653c36a86fb..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/DatabaseStep/DatabaseStep.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { connect } from "react-redux";
-import Settings from "metabase/lib/settings";
-import { DatabaseData } from "metabase-types/api";
-import { State, InviteInfo } from "metabase-types/store";
-import DatabaseStep from "../../components/DatabaseStep";
-import {
-  setDatabaseEngine,
-  setDatabase,
-  setInvite,
-  setStep,
-  submitDatabase,
-} from "../../actions";
-import {
-  trackDatabaseSelected,
-  trackAddDataLaterClicked,
-  trackDatabaseStepCompleted,
-} from "../../analytics";
-import { DATABASE_STEP, PREFERENCES_STEP } from "../../constants";
-import {
-  getDatabase,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  getDatabaseEngine,
-  getInvite,
-  getUser,
-  isLocaleLoaded,
-} from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  user: getUser(state),
-  database: getDatabase(state),
-  engine: getDatabaseEngine(state),
-  invite: getInvite(state),
-  isEmailConfigured: Settings.isEmailConfigured(),
-  isStepActive: isStepActive(state, DATABASE_STEP),
-  isStepCompleted: isStepCompleted(state, DATABASE_STEP),
-  isSetupCompleted: isSetupCompleted(state),
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-const mapDispatchToProps = (dispatch: any) => ({
-  onEngineChange: (engine?: string) => {
-    if (engine) {
-      trackDatabaseSelected(engine);
-    }
-    dispatch(setDatabaseEngine(engine || null));
-  },
-  onStepSelect: () => {
-    dispatch(setStep(DATABASE_STEP));
-  },
-  onDatabaseSubmit: async (database: DatabaseData) => {
-    await dispatch(submitDatabase(database));
-    dispatch(setInvite(null));
-    dispatch(setStep(PREFERENCES_STEP));
-    trackDatabaseStepCompleted(database.engine);
-  },
-  onInviteSubmit: (invite: InviteInfo) => {
-    dispatch(setDatabase(null));
-    dispatch(setInvite(invite));
-    dispatch(setStep(PREFERENCES_STEP));
-    trackDatabaseStepCompleted();
-  },
-  onStepCancel: (engine?: string) => {
-    dispatch(setDatabase(null));
-    dispatch(setInvite(null));
-    dispatch(setStep(PREFERENCES_STEP));
-    trackDatabaseStepCompleted();
-    trackAddDataLaterClicked(engine);
-  },
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps, mapDispatchToProps)(DatabaseStep);
diff --git a/frontend/src/metabase/setup/containers/DatabaseStep/index.ts b/frontend/src/metabase/setup/containers/DatabaseStep/index.ts
deleted file mode 100644
index 60865dfe04ad5602e842886023e91522a0e85325..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/DatabaseStep/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./DatabaseStep";
diff --git a/frontend/src/metabase/setup/containers/LanguageStep/LanguageStep.tsx b/frontend/src/metabase/setup/containers/LanguageStep/LanguageStep.tsx
deleted file mode 100644
index 59c427ece1e8d19533c7d1de0cbdac967f2a36da..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/LanguageStep/LanguageStep.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { connect } from "react-redux";
-import Settings from "metabase/lib/settings";
-import { State, Locale } from "metabase-types/store";
-import LanguageStep from "../../components/LanguageStep";
-import { setLocale, setStep } from "../../actions";
-import { LANGUAGE_STEP, USER_STEP } from "../../constants";
-import {
-  getLocale,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  isLocaleLoaded,
-} from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  locale: getLocale(state),
-  localeData: Settings.get("available-locales") || [],
-  isStepActive: isStepActive(state, LANGUAGE_STEP),
-  isStepCompleted: isStepCompleted(state, LANGUAGE_STEP),
-  isSetupCompleted: isSetupCompleted(state),
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-const mapDispatchToProps = (dispatch: any) => ({
-  onLocaleChange: (locale: Locale) => {
-    dispatch(setLocale(locale));
-  },
-  onStepSelect: () => {
-    dispatch(setStep(LANGUAGE_STEP));
-  },
-  onStepSubmit: () => {
-    dispatch(setStep(USER_STEP));
-  },
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps, mapDispatchToProps)(LanguageStep);
diff --git a/frontend/src/metabase/setup/containers/LanguageStep/index.ts b/frontend/src/metabase/setup/containers/LanguageStep/index.ts
deleted file mode 100644
index 7529f054067afefda7fed0fb4ab5bda6cf350558..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/LanguageStep/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./LanguageStep";
diff --git a/frontend/src/metabase/setup/containers/NewsletterForm/NewsletterForm.tsx b/frontend/src/metabase/setup/containers/NewsletterForm/NewsletterForm.tsx
deleted file mode 100644
index 50e1ee452985e10f4d00d491244656a5a0becd57..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/NewsletterForm/NewsletterForm.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { connect } from "react-redux";
-import { subscribeToNewsletter } from "metabase/setup/utils";
-import NewsletterForm from "../../components/NewsletterForm";
-import { getUserEmail, isLocaleLoaded } from "../../selectors";
-
-const mapStateToProps = (state: any) => ({
-  initialEmail: getUserEmail(state),
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-const mapDispatchToProps = () => ({
-  onSubscribe: subscribeToNewsletter,
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps, mapDispatchToProps)(NewsletterForm);
diff --git a/frontend/src/metabase/setup/containers/NewsletterForm/index.ts b/frontend/src/metabase/setup/containers/NewsletterForm/index.ts
deleted file mode 100644
index f67211d1bc2ddff3b795a366b7fa1317003ffbb2..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/NewsletterForm/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./NewsletterForm";
diff --git a/frontend/src/metabase/setup/containers/PreferencesStep/PreferencesStep.tsx b/frontend/src/metabase/setup/containers/PreferencesStep/PreferencesStep.tsx
deleted file mode 100644
index 4c4c8586c495d4a7c678664661e5645c00877af3..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/PreferencesStep/PreferencesStep.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { connect } from "react-redux";
-import Settings from "metabase/lib/settings";
-import { State } from "metabase-types/store";
-import PreferencesStep from "../../components/PreferencesStep";
-import { setTracking, submitSetup, setStep } from "../../actions";
-import {
-  trackTrackingChanged,
-  trackSetupCompleted,
-  trackPreferencesStepCompleted,
-} from "../../analytics";
-import { PREFERENCES_STEP, COMPLETED_STEP } from "../../constants";
-import {
-  isTrackingAllowed,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  isLocaleLoaded,
-} from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  isTrackingAllowed: isTrackingAllowed(state),
-  isStepActive: isStepActive(state, PREFERENCES_STEP),
-  isStepCompleted: isStepCompleted(state, PREFERENCES_STEP),
-  isSetupCompleted: isSetupCompleted(state),
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-const mapDispatchToProps = (dispatch: any) => ({
-  onTrackingChange: (isTrackingAllowed: boolean) => {
-    dispatch(setTracking(isTrackingAllowed));
-    trackTrackingChanged(isTrackingAllowed);
-    Settings.set("anon-tracking-enabled", isTrackingAllowed);
-    trackTrackingChanged(isTrackingAllowed);
-  },
-  onStepSelect: () => {
-    dispatch(setStep(PREFERENCES_STEP));
-  },
-  onStepSubmit: async (isTrackingAllowed: boolean) => {
-    await dispatch(submitSetup());
-    dispatch(setStep(COMPLETED_STEP));
-    trackPreferencesStepCompleted(isTrackingAllowed);
-    trackSetupCompleted();
-  },
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps, mapDispatchToProps)(PreferencesStep);
diff --git a/frontend/src/metabase/setup/containers/PreferencesStep/index.ts b/frontend/src/metabase/setup/containers/PreferencesStep/index.ts
deleted file mode 100644
index 245a6767d0b888140dd23664a89130449600b8b7..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/PreferencesStep/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./PreferencesStep";
diff --git a/frontend/src/metabase/setup/containers/SettingsPage/SettingsPage.tsx b/frontend/src/metabase/setup/containers/SettingsPage/SettingsPage.tsx
deleted file mode 100644
index c1f300342b260774c5a96d17afd185d6030d171b..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/SettingsPage/SettingsPage.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { connect } from "react-redux";
-import { State } from "metabase-types/store";
-import SettingsPage from "../../components/SettingsPage";
-import { trackStepSeen } from "../../analytics";
-import { getStep, isLocaleLoaded } from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  step: getStep(state),
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-const mapDispatchToProps = () => ({
-  onStepShow: (step: number) => {
-    trackStepSeen(step);
-  },
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps, mapDispatchToProps)(SettingsPage);
diff --git a/frontend/src/metabase/setup/containers/SettingsPage/index.ts b/frontend/src/metabase/setup/containers/SettingsPage/index.ts
deleted file mode 100644
index 4504f4dc20bd05f2d355936ed42d7bb43a0b8177..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/SettingsPage/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./SettingsPage";
diff --git a/frontend/src/metabase/setup/containers/SetupApp/SetupApp.tsx b/frontend/src/metabase/setup/containers/SetupApp/SetupApp.tsx
deleted file mode 100644
index 1252619a4bced40ea37e61cee4fe7ed5efd4c0c3..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/SetupApp/SetupApp.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { connect } from "react-redux";
-import { State } from "metabase-types/store";
-import Setup from "../../components/Setup";
-import { WELCOME_STEP } from "../../constants";
-import { isStepActive } from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  isWelcome: isStepActive(state, WELCOME_STEP),
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps)(Setup);
diff --git a/frontend/src/metabase/setup/containers/SetupApp/index.ts b/frontend/src/metabase/setup/containers/SetupApp/index.ts
deleted file mode 100644
index d9b8af8d63b37d2df9dd8e0ec95065a3cc8f4a59..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/SetupApp/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./SetupApp";
diff --git a/frontend/src/metabase/setup/containers/UserStep/UserStep.tsx b/frontend/src/metabase/setup/containers/UserStep/UserStep.tsx
deleted file mode 100644
index 97909062be9e7124145cda9a0d6612b3f9a1eaed..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/UserStep/UserStep.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { connect } from "react-redux";
-import Settings from "metabase/lib/settings";
-import { State, UserInfo } from "metabase-types/store";
-import UserStep from "../../components/UserStep";
-import { setUser, validatePassword, setStep } from "../../actions";
-import { trackUserStepCompleted } from "../../analytics";
-import { USER_STEP, DATABASE_STEP } from "../../constants";
-import {
-  getUser,
-  isStepActive,
-  isStepCompleted,
-  isSetupCompleted,
-  isLocaleLoaded,
-} from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  user: getUser(state),
-  isHosted: Settings.isHosted(),
-  isStepActive: isStepActive(state, USER_STEP),
-  isStepCompleted: isStepCompleted(state, USER_STEP),
-  isSetupCompleted: isSetupCompleted(state),
-  isLocaleLoaded: isLocaleLoaded(state),
-  onValidatePassword: validatePassword,
-});
-
-const mapDispatchToProps = (dispatch: any) => ({
-  onStepSelect: () => {
-    dispatch(setStep(USER_STEP));
-  },
-  onStepSubmit: (user: UserInfo) => {
-    dispatch(setUser(user));
-    dispatch(setStep(DATABASE_STEP));
-    trackUserStepCompleted();
-  },
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps, mapDispatchToProps)(UserStep);
diff --git a/frontend/src/metabase/setup/containers/UserStep/index.ts b/frontend/src/metabase/setup/containers/UserStep/index.ts
deleted file mode 100644
index 1b53c6e796ef87ee9bd4e450381fb9351ba8e6b2..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/UserStep/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./UserStep";
diff --git a/frontend/src/metabase/setup/containers/WelcomePage/WelcomePage.tsx b/frontend/src/metabase/setup/containers/WelcomePage/WelcomePage.tsx
deleted file mode 100644
index 0c16d248a8b7891e758eba589eb68238a1f80646..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/WelcomePage/WelcomePage.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { connect } from "react-redux";
-import { State } from "metabase-types/store";
-import WelcomePage from "../../components/WelcomePage";
-import { setStep, loadUserDefaults, loadLocaleDefaults } from "../../actions";
-import { trackWelcomeStepCompleted, trackStepSeen } from "../../analytics";
-import { LANGUAGE_STEP, WELCOME_STEP } from "../../constants";
-import { isLocaleLoaded } from "../../selectors";
-
-const mapStateToProps = (state: State) => ({
-  isLocaleLoaded: isLocaleLoaded(state),
-});
-
-const mapDispatchToProps = (dispatch: any) => ({
-  onStepShow: () => {
-    dispatch(loadUserDefaults());
-    dispatch(loadLocaleDefaults());
-    trackStepSeen(WELCOME_STEP);
-  },
-  onStepSubmit: () => {
-    dispatch(setStep(LANGUAGE_STEP));
-    trackWelcomeStepCompleted();
-  },
-});
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default connect(mapStateToProps, mapDispatchToProps)(WelcomePage);
diff --git a/frontend/src/metabase/setup/containers/WelcomePage/index.ts b/frontend/src/metabase/setup/containers/WelcomePage/index.ts
deleted file mode 100644
index 5e8fd9ce42fc56d23bd5f3b0cd2ee13e4fa595fb..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/setup/containers/WelcomePage/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export { default } from "./WelcomePage";
diff --git a/frontend/src/metabase/setup/reducers.ts b/frontend/src/metabase/setup/reducers.ts
index 01fc76d5afb6a9ff4d992c7a0693d305d77ccc5f..91097f28ed52d8b080b9cc24510c38374bafd1b2 100644
--- a/frontend/src/metabase/setup/reducers.ts
+++ b/frontend/src/metabase/setup/reducers.ts
@@ -1,81 +1,78 @@
-import { handleActions } from "redux-actions";
+import { createReducer } from "@reduxjs/toolkit";
+import { SetupState } from "metabase-types/store";
 import {
-  SET_LOCALE,
-  SET_STEP,
-  SET_USER,
-  SET_DATABASE_ENGINE,
-  SET_DATABASE,
-  SET_TRACKING,
-  SET_INVITE,
-  SET_LOCALE_LOADED,
+  skipDatabase,
+  loadLocaleDefaults,
+  loadUserDefaults,
+  selectStep,
+  submitDatabase,
+  submitUser,
+  submitUserInvite,
+  updateDatabaseEngine,
+  updateLocale,
+  updateTracking,
+  submitSetup,
 } from "./actions";
-import { WELCOME_STEP } from "./constants";
-
-export const step = handleActions(
-  {
-    [SET_STEP]: { next: (state, { payload }) => payload },
-  },
+import {
+  COMPLETED_STEP,
+  DATABASE_STEP,
+  PREFERENCES_STEP,
   WELCOME_STEP,
-);
-
-export const locale = handleActions(
-  {
-    [SET_LOCALE]: { next: (state, { payload }) => payload },
-  },
-  null,
-);
-
-export const user = handleActions(
-  {
-    [SET_USER]: { next: (state, { payload }) => payload },
-  },
-  null,
-);
-
-export const databaseEngine = handleActions(
-  {
-    [SET_DATABASE_ENGINE]: { next: (state, { payload }) => payload },
-  },
-  null,
-);
+} from "./constants";
 
-export const database = handleActions(
-  {
-    [SET_DATABASE]: { next: (state, { payload }) => payload },
-  },
-  null,
-);
-
-export const invite = handleActions(
-  {
-    [SET_INVITE]: { next: (state, { payload }) => payload },
-  },
-  null,
-);
-
-export const isLocaleLoaded = handleActions(
-  {
-    [SET_LOCALE]: { next: () => false },
-    [SET_LOCALE_LOADED]: { next: () => true },
-  },
-  false,
-);
-
-export const isTrackingAllowed = handleActions(
-  {
-    [SET_TRACKING]: { next: (state, { payload }) => payload },
-  },
-  true,
-);
-
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default {
-  step,
-  locale,
-  user,
-  database,
-  databaseEngine,
-  invite,
-  isLocaleLoaded,
-  isTrackingAllowed,
+const initialState: SetupState = {
+  step: WELCOME_STEP,
+  isLocaleLoaded: false,
+  isTrackingAllowed: true,
 };
+
+export const reducer = createReducer(initialState, builder => {
+  builder.addCase(loadUserDefaults.fulfilled, (state, { payload: user }) => {
+    state.user = user;
+  });
+  builder.addCase(
+    loadLocaleDefaults.fulfilled,
+    (state, { payload: locale }) => {
+      state.locale = locale;
+      state.isLocaleLoaded = true;
+    },
+  );
+  builder.addCase(selectStep, (state, { payload: step }) => {
+    state.step = step;
+  });
+  builder.addCase(updateLocale.pending, (state, { meta }) => {
+    state.locale = meta.arg;
+    state.isLocaleLoaded = false;
+  });
+  builder.addCase(updateLocale.fulfilled, state => {
+    state.isLocaleLoaded = true;
+  });
+  builder.addCase(submitUser.pending, (state, { meta }) => {
+    state.user = meta.arg;
+    state.step = DATABASE_STEP;
+  });
+  builder.addCase(updateDatabaseEngine.pending, (state, { meta }) => {
+    state.databaseEngine = meta.arg;
+  });
+  builder.addCase(submitDatabase.fulfilled, (state, { payload: database }) => {
+    state.database = database;
+    state.invite = undefined;
+    state.step = PREFERENCES_STEP;
+  });
+  builder.addCase(submitUserInvite.pending, (state, { meta }) => {
+    state.database = undefined;
+    state.invite = meta.arg;
+    state.step = PREFERENCES_STEP;
+  });
+  builder.addCase(skipDatabase.pending, state => {
+    state.database = undefined;
+    state.invite = undefined;
+    state.step = PREFERENCES_STEP;
+  });
+  builder.addCase(updateTracking.pending, (state, { meta }) => {
+    state.isTrackingAllowed = meta.arg;
+  });
+  builder.addCase(submitSetup.fulfilled, state => {
+    state.step = COMPLETED_STEP;
+  });
+});
diff --git a/frontend/src/metabase/setup/selectors.ts b/frontend/src/metabase/setup/selectors.ts
index 16976e07756bb8d10ff5eb41d0e60d8909429a23..463c661781295201e7f3e0d805d54e875525ef11 100644
--- a/frontend/src/metabase/setup/selectors.ts
+++ b/frontend/src/metabase/setup/selectors.ts
@@ -1,7 +1,10 @@
-import { DatabaseData } from "metabase-types/api";
+import { DatabaseData, LocaleData } from "metabase-types/api";
 import { InviteInfo, Locale, State, UserInfo } from "metabase-types/store";
+import { getSetting } from "metabase/selectors/settings";
 import { COMPLETED_STEP } from "./constants";
 
+const DEFAULT_LOCALES: LocaleData[] = [];
+
 export const getStep = (state: State): number => {
   return state.setup.step;
 };
@@ -26,26 +29,42 @@ export const getInvite = (state: State): InviteInfo | undefined => {
   return state.setup.invite;
 };
 
-export const isLocaleLoaded = (state: State): boolean => {
+export const getIsLocaleLoaded = (state: State): boolean => {
   return state.setup.isLocaleLoaded;
 };
 
-export const isTrackingAllowed = (state: State): boolean => {
+export const getIsTrackingAllowed = (state: State): boolean => {
   return state.setup.isTrackingAllowed;
 };
 
-export const isStepActive = (state: State, step: number): boolean => {
+export const getIsStepActive = (state: State, step: number): boolean => {
   return getStep(state) === step;
 };
 
-export const isStepCompleted = (state: State, step: number): boolean => {
+export const getIsStepCompleted = (state: State, step: number): boolean => {
   return getStep(state) > step;
 };
 
-export const isSetupCompleted = (state: State): boolean => {
+export const getIsSetupCompleted = (state: State): boolean => {
   return getStep(state) === COMPLETED_STEP;
 };
 
 export const getDatabaseEngine = (state: State): string | undefined => {
   return getDatabase(state)?.engine || state.setup.databaseEngine;
 };
+
+export const getSetupToken = (state: State) => {
+  return getSetting(state, "setup-token");
+};
+
+export const getIsHosted = (state: State): boolean => {
+  return getSetting(state, "is-hosted?");
+};
+
+export const getAvailableLocales = (state: State): LocaleData[] => {
+  return getSetting(state, "available-locales") ?? DEFAULT_LOCALES;
+};
+
+export const getIsEmailConfigured = (state: State): boolean => {
+  return getSetting(state, "email-configured?");
+};
diff --git a/frontend/src/metabase/setup/utils.ts b/frontend/src/metabase/setup/utils.ts
index 3bcce76832431128fb4f4be4161aef07e914d429..8452c335b30e107cdb4820a9eadf460a23f74e03 100644
--- a/frontend/src/metabase/setup/utils.ts
+++ b/frontend/src/metabase/setup/utils.ts
@@ -1,6 +1,10 @@
+import { getIn } from "icepick";
 import _ from "underscore";
+import { UtilApi } from "metabase/services";
+import MetabaseSettings from "metabase/lib/settings";
 import { LocaleData } from "metabase-types/api";
 import { Locale } from "metabase-types/store";
+import { SUBSCRIBE_URL, SUBSCRIBE_TOKEN } from "./constants";
 
 export const getLocales = (
   localeData: LocaleData[] = [["en", "English"]],
@@ -28,9 +32,18 @@ export const getUserToken = (hash = window.location.hash): string => {
   return hash.replace(/^#/, "");
 };
 
-const SUBSCRIBE_URL =
-  "https://metabase.us10.list-manage.com/subscribe/post?u=869fec0e4689e8fd1db91e795&id=b9664113a8";
-const SUBSCRIBE_TOKEN = "b_869fec0e4689e8fd1db91e795_b9664113a8";
+export const validatePassword = async (password: string) => {
+  const error = MetabaseSettings.passwordComplexityDescription(password);
+  if (error) {
+    return error;
+  }
+
+  try {
+    await UtilApi.password_check({ password });
+  } catch (error) {
+    return getIn(error, ["data", "errors", "password"]);
+  }
+};
 
 export const subscribeToNewsletter = async (email: string): Promise<void> => {
   const body = new FormData();
diff --git a/frontend/test/__support__/server-mocks/index.ts b/frontend/test/__support__/server-mocks/index.ts
index 210abb9657803e9e7a7f730b355afa41b2fd2fca..013d585cb9d9409fe05cbce75263670a9d87266e 100644
--- a/frontend/test/__support__/server-mocks/index.ts
+++ b/frontend/test/__support__/server-mocks/index.ts
@@ -10,6 +10,7 @@ export * from "./dashboard";
 export * from "./database";
 export * from "./dataset";
 export * from "./field";
+export * from "./group";
 export * from "./metabot";
 export * from "./metric";
 export * from "./model-indexes";
diff --git a/frontend/test/__support__/server-mocks/setup.ts b/frontend/test/__support__/server-mocks/setup.ts
index 99721a4e0d2fd3b0fc7354a3ff6f2459f7841fcd..0492a17ca569c6a07ed68025b43afea594c5fb01 100644
--- a/frontend/test/__support__/server-mocks/setup.ts
+++ b/frontend/test/__support__/server-mocks/setup.ts
@@ -1,6 +1,10 @@
 import fetchMock from "fetch-mock";
 import { SetupCheckListItem } from "metabase-types/api";
 
+export function setupErrorSetupEndpoints() {
+  fetchMock.post("path:/api/setup", 400);
+}
+
 export function setupAdminCheckListEndpoint(items: SetupCheckListItem[]) {
   fetchMock.get("path:/api/setup/admin_checklist", items);
 }