diff --git a/web/src/App.tsx b/web/src/App.tsx
index 06b45ba55d..6d920227f8 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2022-2024] SUSE LLC
+ * Copyright (c) [2022-2025] SUSE LLC
*
* All Rights Reserved.
*
@@ -34,12 +34,13 @@ import { useDeprecatedChanges } from "~/queries/storage";
import { useRootUser } from "~/queries/users";
import { ROOT, PRODUCT, USER } from "~/routes/paths";
import { InstallationPhase } from "~/types/status";
-import { isEmpty } from "~/utils";
+import { isEmpty, useLocalStorage } from "~/utils";
/**
* Main application component.
*/
function App() {
+ const [showWelcomePage] = useLocalStorage("agm-show-welcome-page", true);
const location = useLocation();
const { isBusy, phase } = useInstallerStatus({ suspense: true });
const { connected, error } = useInstallerClientStatus();
@@ -55,6 +56,10 @@ function App() {
const Content = () => {
if (error) return ;
+ if (showWelcomePage) {
+ return ;
+ }
+
if (phase === InstallationPhase.Install && isBusy) {
return ;
}
diff --git a/web/src/components/core/WelcomePage.tsx b/web/src/components/core/WelcomePage.tsx
new file mode 100644
index 0000000000..dad8a37a1e
--- /dev/null
+++ b/web/src/components/core/WelcomePage.tsx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) [2025] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React, { useEffect } from "react";
+import { Form, FormGroup, FormSelectOption, Grid, GridItem } from "@patternfly/react-core";
+import { useNavigate } from "react-router-dom";
+import { Page } from "~/components/core";
+import { useInstallerL10n } from "~/context/installerL10n";
+// import { ROOT as PATHS } from "~/routes/paths";
+import { _ } from "~/i18n";
+import supportedLanguages from "~/languages.json";
+import { Center, Icon } from "../layout";
+import { useLocalStorage } from "~/utils";
+import { ROOT as PATHS } from "~/routes/paths";
+
+/**
+ * A page component to allow setting the installer langauge
+ */
+function WelcomePage() {
+ const { language, changeLanguage } = useInstallerL10n();
+ const [showWelcomePage, setShowWelcomePage] = useLocalStorage("agm-show-welcome-page");
+ const navigate = useNavigate();
+
+ const onSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ const formData = new FormData(e.currentTarget);
+ await changeLanguage(formData.get("language") as string);
+ setShowWelcomePage(false);
+ // navigate(PATHS.root, { replace: true });
+ };
+
+ useEffect(() => {
+ !showWelcomePage && navigate(PATHS.root, { replace: true });
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default WelcomePage;
diff --git a/web/src/components/core/index.ts b/web/src/components/core/index.ts
index dd18a31925..959f6b3d47 100644
--- a/web/src/components/core/index.ts
+++ b/web/src/components/core/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2022-2024] SUSE LLC
+ * Copyright (c) [2022-2025] SUSE LLC
*
* All Rights Reserved.
*
@@ -49,5 +49,6 @@ export { default as TreeTable } from "./TreeTable";
export { default as Link } from "./Link";
export { default as EmptyState } from "./EmptyState";
export { default as InstallerOptions } from "./InstallerOptions";
+export { default as WelcomePage } from "./WelcomePage";
export { default as IssuesDrawer } from "./IssuesDrawer";
export { default as Drawer } from "./Drawer";
diff --git a/web/src/router.tsx b/web/src/router.tsx
index 55ec7f0ce2..dc7539efaa 100644
--- a/web/src/router.tsx
+++ b/web/src/router.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2024] SUSE LLC
+ * Copyright (c) [2024-2025] SUSE LLC
*
* All Rights Reserved.
*
@@ -25,7 +25,12 @@ import { createHashRouter, Outlet } from "react-router-dom";
import App from "~/App";
import Protected from "~/Protected";
import { FullLayout, PlainLayout } from "~/components/layout";
-import { InstallationFinished, InstallationProgress, LoginPage } from "~/components/core";
+import {
+ InstallationFinished,
+ InstallationProgress,
+ LoginPage,
+ WelcomePage,
+} from "~/components/core";
import { OverviewPage } from "~/components/overview";
import l10nRoutes from "~/routes/l10n";
import networkRoutes from "~/routes/network";
@@ -86,6 +91,10 @@ const protectedRoutes = () => [
),
children: [
+ {
+ path: PATHS.welcomePage,
+ element: ,
+ },
{
path: PATHS.installationProgress,
element: ,
diff --git a/web/src/routes/paths.ts b/web/src/routes/paths.ts
index efe67522a4..7093bbb794 100644
--- a/web/src/routes/paths.ts
+++ b/web/src/routes/paths.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2024] SUSE LLC
+ * Copyright (c) [2024-2025] SUSE LLC
*
* All Rights Reserved.
*
@@ -51,6 +51,7 @@ const ROOT = {
installationProgress: "/installation/progress",
installationFinished: "/installation/finished",
logs: "/api/manager/logs/store",
+ welcomePage: "/welcome",
};
const USER = {
@@ -83,6 +84,7 @@ const STORAGE = {
};
const SUPPORTIVE_PATHS = [
+ ROOT.welcomePage,
ROOT.login,
PRODUCT.changeProduct,
PRODUCT.progress,
diff --git a/web/src/utils.ts b/web/src/utils.ts
index 81ab64c3f8..4808def554 100644
--- a/web/src/utils.ts
+++ b/web/src/utils.ts
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2022-2024] SUSE LLC
+ * Copyright (c) [2022-2025] SUSE LLC
*
* All Rights Reserved.
*
@@ -261,7 +261,7 @@ const useCancellablePromise = () => {
* @param storageKey
* @param fallbackState
*/
-const useLocalStorage = (storageKey: string, fallbackState) => {
+const useLocalStorage = (storageKey: string, fallbackState?) => {
const [value, setValue] = useState(JSON.parse(localStorage.getItem(storageKey)) ?? fallbackState);
useEffect(() => {