Skip to content

chore(web): consistent usage of the intial state context #2460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 14 additions & 15 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LinuxConfigProvider } from "./contexts/LinuxConfigContext";
import { KubernetesConfigProvider } from "./contexts/KubernetesConfigContext";
import { SettingsProvider } from "./contexts/SettingsContext";
import { WizardProvider } from "./contexts/WizardModeContext";
import { BrandingProvider } from "./contexts/BrandingContext";
import { InitialStateProvider } from "./contexts/InitialStateContext";
import { AuthProvider } from "./contexts/AuthContext";
import ConnectionMonitor from "./components/common/ConnectionMonitor";
import InstallWizard from "./components/wizard/InstallWizard";
Expand All @@ -13,12 +13,12 @@ import { getQueryClient } from "./query-client";
function App() {
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<SettingsProvider>
<LinuxConfigProvider>
<KubernetesConfigProvider>
<BrandingProvider>
<InitialStateProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<SettingsProvider>
<LinuxConfigProvider>
<KubernetesConfigProvider>
<div className="min-h-screen bg-gray-50 text-gray-900 font-sans">
<BrowserRouter>
<Routes>
Expand All @@ -30,18 +30,17 @@ function App() {
</WizardProvider>
}
/>

<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</div>
</BrandingProvider>
</KubernetesConfigProvider>
</LinuxConfigProvider>
</SettingsProvider>
</AuthProvider>
<ConnectionMonitor />
</QueryClientProvider>
</KubernetesConfigProvider>
</LinuxConfigProvider>
</SettingsProvider>
</AuthProvider>
<ConnectionMonitor />
</QueryClientProvider>
</InitialStateProvider>
);
}

Expand Down
4 changes: 2 additions & 2 deletions web/src/components/common/Logo.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';

import { useBranding } from '../../contexts/BrandingContext';
import { useInitialState } from '../../contexts/InitialStateContext';

export const AppIcon: React.FC<{ className?: string }> = ({ className = 'w-6 h-6' }) => {
const { icon } = useBranding();
const { icon } = useInitialState();
if (!icon) {
return <div className="h-6 w-6 bg-gray-200 rounded"></div>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, {useState} from "react";
import React, { useState } from "react";
import Card from "../../common/Card";
import Button from "../../common/Button";
import { useKubernetesConfig } from "../../../contexts/KubernetesConfigContext";
import { useBranding } from "../../../contexts/BrandingContext";
import { useInitialState } from "../../../contexts/InitialStateContext";
import { useSettings } from "../../../contexts/SettingsContext";
import { CheckCircle, ClipboardCheck, Copy, Terminal } from "lucide-react";

const KubernetesCompletionStep: React.FC = () => {
const [copied, setCopied] = useState(false);
const { config } = useKubernetesConfig();
const { title } = useBranding();
const { title } = useInitialState();
const { settings } = useSettings();
const themeColor = settings.themeColor;

Expand Down
4 changes: 2 additions & 2 deletions web/src/components/wizard/completion/LinuxCompletionStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import React from "react";
import Card from "../../common/Card";
import Button from "../../common/Button";
import { useLinuxConfig } from "../../../contexts/LinuxConfigContext";
import { useBranding } from "../../../contexts/BrandingContext";
import { useInitialState } from "../../../contexts/InitialStateContext";
import { useSettings } from "../../../contexts/SettingsContext";
import { CheckCircle, ExternalLink } from "lucide-react";

const LinuxCompletionStep: React.FC = () => {
const { config } = useLinuxConfig();
const { title } = useBranding();
const { title } = useInitialState();
const { settings } = useSettings();
const themeColor = settings.themeColor;

Expand Down
48 changes: 24 additions & 24 deletions web/src/components/wizard/setup/LinuxSetupStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Input from "../../common/Input";
import Select from "../../common/Select";
import Button from "../../common/Button";
import Card from "../../common/Card";
import { useBranding } from "../../../contexts/BrandingContext";
import { useInitialState } from "../../../contexts/InitialStateContext";
import { useLinuxConfig } from "../../../contexts/LinuxConfigContext";
import { useWizard } from "../../../contexts/WizardModeContext";
import { useQuery, useMutation } from "@tanstack/react-query";
Expand All @@ -19,17 +19,17 @@ import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
* - Error formatting: formatErrorMessage("adminConsolePort invalid") -> "Admin Console Port invalid"
*/
const fieldNames = {
adminConsolePort: "Admin Console Port",
dataDirectory: "Data Directory",
localArtifactMirrorPort: "Local Artifact Mirror Port",
httpProxy: "HTTP Proxy",
httpsProxy: "HTTPS Proxy",
noProxy: "Proxy Bypass List",
networkInterface: "Network Interface",
podCidr: "Pod CIDR",
serviceCidr: "Service CIDR",
globalCidr: "Reserved Network Range (CIDR)",
cidr: "CIDR",
adminConsolePort: "Admin Console Port",
dataDirectory: "Data Directory",
localArtifactMirrorPort: "Local Artifact Mirror Port",
httpProxy: "HTTP Proxy",
httpsProxy: "HTTPS Proxy",
noProxy: "Proxy Bypass List",
networkInterface: "Network Interface",
podCidr: "Pod CIDR",
serviceCidr: "Service CIDR",
globalCidr: "Reserved Network Range (CIDR)",
cidr: "CIDR",
}

interface LinuxSetupStepProps {
Expand All @@ -49,7 +49,7 @@ interface ConfigError extends Error {
const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext, onBack }) => {
const { config, updateConfig } = useLinuxConfig();
const { text } = useWizard();
const { title } = useBranding();
const { title } = useInitialState();
const [showAdvanced, setShowAdvanced] = useState(false);
const [error, setError] = useState<string | null>(null);
const { token } = useAuth();
Expand Down Expand Up @@ -269,9 +269,9 @@ const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext, onBack }) => {
options={[
...(availableNetworkInterfaces.length > 0
? availableNetworkInterfaces.map((iface: string) => ({
value: iface,
label: iface,
}))
value: iface,
label: iface,
}))
: []),
]}
helpText={`Network interface to use for ${title}`}
Expand All @@ -296,7 +296,7 @@ const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext, onBack }) => {

{error && (
<div className="mt-4 p-3 bg-red-50 text-red-500 rounded-md">
{submitError?.errors && submitError.errors.length > 0
{submitError?.errors && submitError.errors.length > 0
? "Please fix the errors in the form above before proceeding."
: error
}
Expand Down Expand Up @@ -326,13 +326,13 @@ const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext, onBack }) => {
* @returns The formatted error message with replaced field names
*/
export function formatErrorMessage(message: string) {
let finalMsg = message
for (const [field, fieldName] of Object.entries(fieldNames)) {
// Case-insensitive regex that matches whole words only
// Example: "podCidr", "PodCidr", "PODCIDR" all become "Pod CIDR"
finalMsg = finalMsg.replace(new RegExp(`\\b${field}\\b`, 'gi'), fieldName)
}
return finalMsg
let finalMsg = message
for (const [field, fieldName] of Object.entries(fieldNames)) {
// Case-insensitive regex that matches whole words only
// Example: "podCidr", "PodCidr", "PODCIDR" all become "Pod CIDR"
finalMsg = finalMsg.replace(new RegExp(`\\b${field}\\b`, 'gi'), fieldName)
}
return finalMsg
}

export default LinuxSetupStep;
9 changes: 4 additions & 5 deletions web/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { createContext, useContext, useState, useEffect } from "react";
import { handleUnauthorized } from "../utils/auth";
import { useInitialState } from "./InitialStateContext";

interface AuthContextType {
token: string | null;
Expand Down Expand Up @@ -32,17 +33,15 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
}
setTokenState(newToken);
};
// Get the installation target from initial state
const { installTarget } = useInitialState()

// Check token validity on mount and when token changes
useEffect(() => {
if (token) {
// Get the installation target from initial state
const initialState = window.__INITIAL_STATE__ || {};
const target = initialState.installTarget;

// Make a request to any authenticated endpoint to check token validity
// Use the correct target-specific endpoint based on installation target
fetch(`/api/${target}/install/installation/config`, {
fetch(`/api/${installTarget}/install/installation/config`, {
headers: {
Authorization: `Bearer ${token}`,
},
Expand Down
35 changes: 0 additions & 35 deletions web/src/contexts/BrandingContext.tsx

This file was deleted.

40 changes: 40 additions & 0 deletions web/src/contexts/InitialStateContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { createContext, useContext } from "react";
import { InitialState } from "../types";
import { InstallationTarget, isInstallationTarget } from "../types/installation-target";

export const InitialStateContext = createContext<InitialState>({ title: "My App", installTarget: "linux" });

export const useInitialState = () => {
const context = useContext(InitialStateContext);
if (!context) {
throw new Error("useInitialState must be used within a InitialStateProvider");
}
return context;
};

function parseInstallationTarget(target: string): InstallationTarget {
if (isInstallationTarget(target)) {
return target;
}
throw new Error(`Invalid installation target: ${target}`);
}

export const InitialStateProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
// __INITIAL_STATE__ is a global variable that can be set by the server-side rendering process
// as a way to pass initial data to the client.
const initialState = window.__INITIAL_STATE__ || {};

const state = {
title: initialState.title || "My App",
icon: initialState.icon,
installTarget: parseInstallationTarget(initialState.installTarget || "linux"), // default to "linux" if not provided
};

return (
<InitialStateContext.Provider value={state}>
{children}
</InitialStateContext.Provider>
);
};
17 changes: 6 additions & 11 deletions web/src/contexts/WizardModeContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { createContext, useContext } from "react";
import { useBranding } from "./BrandingContext";
import { useInitialState } from "./InitialStateContext";
import { InstallationTarget } from "../types/installation-target";

export type WizardMode = "install" | "upgrade";
export type WizardTarget = "linux" | "kubernetes";

interface WizardText {
title: string;
Expand Down Expand Up @@ -59,25 +59,20 @@ const getTextVariations = (isLinux: boolean, title: string): Record<WizardMode,
});

interface WizardModeContextType {
target: WizardTarget;
target: InstallationTarget;
mode: WizardMode;
text: WizardText;
}

export const WizardContext = createContext<WizardModeContextType | undefined>(undefined);

export const WizardProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
// __INITIAL_STATE__ is a global variable that can be set by the server-side rendering process
// as a way to pass initial data to the client.
const initialState = window.__INITIAL_STATE__ || {};
const target: WizardTarget = initialState.installTarget as WizardTarget;
const { title, installTarget } = useInitialState();
const mode = "install"; // TODO: get mode from initial state

const { title } = useBranding();
const isLinux = target === "linux";
const isLinux = installTarget === "linux";
const text = getTextVariations(isLinux, title)[mode];

return <WizardContext.Provider value={{ mode, target, text }}>{children}</WizardContext.Provider>;
return <WizardContext.Provider value={{ mode, target: installTarget, text }}>{children}</WizardContext.Provider>;
};

export const useWizard = (): WizardModeContextType => {
Expand Down
Loading