- Added support for mock data mode in the frontend by introducing a new environment variable in the Dockerfile and workflow configuration. - Updated the settings page to conditionally display options based on the availability of mock data, improving user experience. - Refactored data source logic to include a check for mock data availability, ensuring proper mode handling in the application.
111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
"use client"
|
|
|
|
import { createContext, useContext, useEffect, useState, useCallback } from "react"
|
|
import {
|
|
configuredBackendUrl,
|
|
defaultDataSourceMode,
|
|
isBackendUrlLocked,
|
|
isMockDataSourceAvailable,
|
|
LOCAL_DEFAULT_BACKEND_URL,
|
|
resolveStoredBackendUrl,
|
|
} from "@/lib/backend-url"
|
|
|
|
// ── types ─────────────────────────────────────────────────────────────────────
|
|
|
|
export type DataSourceMode = "mock" | "live"
|
|
|
|
interface DataSourceContextValue {
|
|
mode: DataSourceMode
|
|
setMode: (m: DataSourceMode) => void
|
|
backendUrl: string
|
|
setBackendUrl: (url: string) => void
|
|
backendUrlLocked: boolean
|
|
mockModeAvailable: boolean
|
|
/** undefined = не проверялось, true = OK, false = недоступен */
|
|
backendStatus: boolean | undefined
|
|
checkBackend: () => Promise<void>
|
|
}
|
|
|
|
// ── context ───────────────────────────────────────────────────────────────────
|
|
|
|
const DataSourceContext = createContext<DataSourceContextValue | null>(null)
|
|
|
|
const LS_MODE = "routerlists:data-source"
|
|
const LS_BACKEND = "routerlists:backend-url"
|
|
|
|
function readStoredMode(): DataSourceMode {
|
|
if (!isMockDataSourceAvailable()) return "live"
|
|
if (typeof window === "undefined") return defaultDataSourceMode()
|
|
const stored = localStorage.getItem(LS_MODE) as DataSourceMode | null
|
|
if (stored === "live") return "live"
|
|
if (stored === "mock") return "mock"
|
|
return defaultDataSourceMode()
|
|
}
|
|
|
|
function readStoredBackendUrl(): string {
|
|
if (typeof window === "undefined") return LOCAL_DEFAULT_BACKEND_URL
|
|
return resolveStoredBackendUrl(localStorage.getItem(LS_BACKEND))
|
|
}
|
|
|
|
function normalizeBackendUrl(url: string): string {
|
|
return url.trim().replace(/\/$/, "")
|
|
}
|
|
|
|
export function DataSourceProvider({ children }: { children: React.ReactNode }) {
|
|
const [mode, setModeState] = useState<DataSourceMode>(() =>
|
|
typeof window === "undefined" ? defaultDataSourceMode() : readStoredMode(),
|
|
)
|
|
const [backendUrl, setBackendUrlState] = useState(() =>
|
|
typeof window === "undefined" ? LOCAL_DEFAULT_BACKEND_URL : readStoredBackendUrl(),
|
|
)
|
|
const [backendStatus, setBackendStatus] = useState<boolean | undefined>(undefined)
|
|
const backendUrlLocked = isBackendUrlLocked()
|
|
const mockModeAvailable = isMockDataSourceAvailable()
|
|
|
|
useEffect(() => {
|
|
if (configuredBackendUrl().kind !== "same-origin") return
|
|
setBackendUrlState(window.location.origin)
|
|
}, [])
|
|
|
|
const setMode = useCallback((m: DataSourceMode) => {
|
|
if (!mockModeAvailable && m === "mock") return
|
|
setModeState(m)
|
|
localStorage.setItem(LS_MODE, m)
|
|
}, [mockModeAvailable])
|
|
|
|
const setBackendUrl = useCallback((url: string) => {
|
|
if (backendUrlLocked) return
|
|
const normalized = normalizeBackendUrl(url)
|
|
setBackendUrlState(normalized)
|
|
localStorage.setItem(LS_BACKEND, normalized)
|
|
}, [backendUrlLocked])
|
|
|
|
const checkBackend = useCallback(async () => {
|
|
const url = normalizeBackendUrl(backendUrl)
|
|
try {
|
|
const res = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3000) })
|
|
setBackendStatus(res.ok)
|
|
} catch {
|
|
setBackendStatus(false)
|
|
}
|
|
}, [backendUrl])
|
|
|
|
useEffect(() => {
|
|
if (mode === "live") queueMicrotask(() => { void checkBackend() })
|
|
}, [mode, checkBackend])
|
|
|
|
return (
|
|
<DataSourceContext.Provider
|
|
value={{ mode, setMode, backendUrl, setBackendUrl, backendUrlLocked, mockModeAvailable, backendStatus, checkBackend }}
|
|
>
|
|
{children}
|
|
</DataSourceContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useDataSource(): DataSourceContextValue {
|
|
const ctx = useContext(DataSourceContext)
|
|
if (!ctx) throw new Error("useDataSource must be used inside <DataSourceProvider>")
|
|
return ctx
|
|
}
|