diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 3ba63cc2..b4171110 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -16,6 +16,11 @@ services: - ../api/migrations:/app/migrations #- ../api/node_modules:/app/node_modules command: ["npm", "run", "start:debug"] + + hexabot-frontend: + build: + context: ../ + dockerfile: ./frontend/Dockerfile mongo-express: container_name: mongoUi diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 80102c66..5549acf3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -30,7 +30,6 @@ services: condition: service_healthy database-init: condition: service_completed_successfully - healthcheck: test: "wget --spider http://localhost:3000" interval: 10s @@ -40,12 +39,7 @@ services: hexabot-frontend: container_name: frontend - build: - context: ../ - dockerfile: ./frontend/Dockerfile - args: - - NEXT_PUBLIC_API_ORIGIN=${NEXT_PUBLIC_API_ORIGIN} - - NEXT_PUBLIC_SSO_ENABLED=${NEXT_PUBLIC_SSO_ENABLED} + image: hexabot-ui:latest env_file: .env ports: - ${APP_FRONTEND_PORT}:8080 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index cea1c11b..d8d812c6 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -21,14 +21,6 @@ RUN \ # Rebuild the source code only when needed FROM base AS builder -ARG NEXT_PUBLIC_API_ORIGIN -ENV NEXT_PUBLIC_API_ORIGIN=${NEXT_PUBLIC_API_ORIGIN} -ARG NEXT_PUBLIC_SSO_ENABLED -ENV NEXT_PUBLIC_SSO_ENABLED=${NEXT_PUBLIC_SSO_ENABLED} - -ENV REACT_APP_WIDGET_API_URL=${NEXT_PUBLIC_API_ORIGIN} -ENV REACT_APP_WIDGET_CHANNEL=test -ENV REACT_APP_WIDGET_TOKEN=test WORKDIR /app @@ -57,10 +49,6 @@ ENV NODE_ENV production # Uncomment the following line in case you want to disable telemetry during runtime. ENV NEXT_TELEMETRY_DISABLED 1 -# Set the environment variable API_ORIGIN -ENV NEXT_PUBLIC_API_ORIGIN ${NEXT_PUBLIC_API_ORIGIN:-"http://localhost:3000"} -ENV NEXT_PUBLIC_SSO_ENABLED ${NEXT_PUBLIC_SSO_ENABLED:-"false"} - RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index 774cf83f..f1c7e508 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -4,6 +4,14 @@ import withTM from "next-transpile-modules"; const apiUrl = process.env.NEXT_PUBLIC_API_ORIGIN || "http://localhost:4000/"; const url = new URL(apiUrl); const nextConfig = withTM(["hexabot-widget"])({ + async rewrites() { + return [ + { + source: "/config", + destination: "/api/config", + }, + ]; + }, webpack(config, _options) { return config; }, diff --git a/frontend/src/hooks/useConfig.tsx b/frontend/src/hooks/useConfig.tsx new file mode 100644 index 00000000..f8a4fd94 --- /dev/null +++ b/frontend/src/hooks/useConfig.tsx @@ -0,0 +1,50 @@ +import { createContext, useContext, useEffect, useState } from "react"; + +const ConfigContext = createContext(null); + +export interface IConfig { + NEXT_PUBLIC_API_ORIGIN: string; + NEXT_PUBLIC_SSO_ENABLED: boolean; + REACT_APP_WIDGET_API_URL: string; + REACT_APP_WIDGET_CHANNEL: string; + REACT_APP_WIDGET_TOKEN: string; +} + +export const ConfigProvider = ({ children }) => { + const [config, setConfig] = useState(null); + + useEffect(() => { + const fetchConfig = async () => { + try { + const res = await fetch("/config"); + const data = await res.json(); + + setConfig(data); + } catch (error) { + // eslint-disable-next-line no-console + console.error("Failed to fetch configuration:", error); + } + }; + + fetchConfig(); + }, []); + + if (!config) { + // You can return a loader here if you want + return null; + } + + return ( + {children} + ); +}; + +export const useConfig = () => { + const context = useContext(ConfigContext); + + if (!context) { + throw new Error("useConfig must be used within a ConfigProvider"); + } + + return context; +}; diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index ad0bbab1..b93e392d 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -20,6 +20,7 @@ import { ReactQueryDevtools } from "react-query/devtools"; import { SnackbarCloseButton } from "@/app-components/displays/Toast/CloseButton"; import { ApiClientProvider } from "@/hooks/useApiClient"; import { AuthProvider } from "@/hooks/useAuth"; +import { ConfigProvider } from "@/hooks/useConfig"; import { PermissionProvider } from "@/hooks/useHasPermission"; import { SettingsProvider } from "@/hooks/useSetting"; import { ToastProvider } from "@/hooks/useToast"; @@ -69,33 +70,35 @@ const App = ({ Component, pageProps }: TAppPropsWithLayout) => { />
- - ( - - )} - > - - - - - - - - - {getLayout()} - - - - - - - - - - + + + ( + + )} + > + + + + + + + + + {getLayout()} + + + + + + + + + + +
); diff --git a/frontend/src/pages/api/config.ts b/frontend/src/pages/api/config.ts new file mode 100644 index 00000000..2af6d1ab --- /dev/null +++ b/frontend/src/pages/api/config.ts @@ -0,0 +1,16 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +type ResponseData = { + apiUrl: string; + ssoEnabled: boolean; +}; + +export default function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + res.status(200).json({ + apiUrl: process.env.NEXT_PUBLIC_API_ORIGIN || "http://localhost:3000", + ssoEnabled: process.env.NEXT_PUBLIC_SSO_ENABLED === "true" || false, + }); +}