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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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 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 @@
* - 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 @@
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 @@
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 @@

{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 @@ -325,14 +325,14 @@
* @param message - The error message to format
* @returns The formatted error message with replaced field names
*/
export function formatErrorMessage(message: string) {

Check warning on line 328 in web/src/components/wizard/setup/LinuxSetupStep.tsx

View workflow job for this annotation

GitHub Actions / Web unit tests

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
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 All @@ -8,9 +9,9 @@
isLoading: boolean;
}

export const AuthContext = createContext<AuthContextType | undefined>(undefined);

Check warning on line 12 in web/src/contexts/AuthContext.tsx

View workflow job for this annotation

GitHub Actions / Web unit tests

Fast refresh only works when a file only exports components. Move your React context(s) to a separate file

export const useAuth = () => {

Check warning on line 14 in web/src/contexts/AuthContext.tsx

View workflow job for this annotation

GitHub Actions / Web unit tests

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
Expand All @@ -32,17 +33,15 @@
}
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 All @@ -68,7 +67,7 @@
} else {
setIsLoading(false);
}
}, [token]);

Check warning on line 70 in web/src/contexts/AuthContext.tsx

View workflow job for this annotation

GitHub Actions / Web unit tests

React Hook useEffect has a missing dependency: 'installTarget'. Either include it or remove the dependency array

useEffect(() => {
// Listen for storage events to sync token state across tabs
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" });

Check warning on line 5 in web/src/contexts/InitialStateContext.tsx

View workflow job for this annotation

GitHub Actions / Web unit tests

Fast refresh only works when a file only exports components. Move your React context(s) to a separate file

export const useInitialState = () => {

Check warning on line 7 in web/src/contexts/InitialStateContext.tsx

View workflow job for this annotation

GitHub Actions / Web unit tests

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
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
Loading