Experimental gui.
This commit is contained in:
165
experimental/app/src/hooks/__tests__/useConfig.test.ts
Normal file
165
experimental/app/src/hooks/__tests__/useConfig.test.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, waitFor, act } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import { useConfig } from '../useConfig';
|
||||
import { apiService } from '../../services/api';
|
||||
|
||||
// Mock the API service
|
||||
vi.mock('../../services/api', () => ({
|
||||
apiService: {
|
||||
getConfig: vi.fn(),
|
||||
createConfig: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
gcTime: 0,
|
||||
},
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children)
|
||||
);
|
||||
};
|
||||
|
||||
describe('useConfig', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should fetch config successfully when configured', async () => {
|
||||
const mockConfigResponse = {
|
||||
configured: true,
|
||||
config: {
|
||||
server: { host: '0.0.0.0', port: 5055 },
|
||||
cloud: {
|
||||
domain: 'wildcloud.local',
|
||||
internalDomain: 'cluster.local',
|
||||
dhcpRange: '192.168.8.100,192.168.8.200',
|
||||
dns: { ip: '192.168.8.50' },
|
||||
router: { ip: '192.168.8.1' },
|
||||
dnsmasq: { interface: 'eth0' },
|
||||
},
|
||||
cluster: {
|
||||
endpointIp: '192.168.8.60',
|
||||
nodes: { talos: { version: 'v1.8.0' } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(apiService.getConfig).mockResolvedValue(mockConfigResponse);
|
||||
|
||||
const { result } = renderHook(() => useConfig(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
expect(result.current.showConfigSetup).toBe(false);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.config).toEqual(mockConfigResponse.config);
|
||||
expect(result.current.isConfigured).toBe(true);
|
||||
expect(result.current.showConfigSetup).toBe(false);
|
||||
expect(result.current.error).toBeNull();
|
||||
});
|
||||
|
||||
it('should show config setup when not configured', async () => {
|
||||
const mockConfigResponse = {
|
||||
configured: false,
|
||||
message: 'No configuration found',
|
||||
};
|
||||
|
||||
vi.mocked(apiService.getConfig).mockResolvedValue(mockConfigResponse);
|
||||
|
||||
const { result } = renderHook(() => useConfig(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.config).toBeNull();
|
||||
expect(result.current.isConfigured).toBe(false);
|
||||
expect(result.current.showConfigSetup).toBe(true);
|
||||
});
|
||||
|
||||
it('should create config successfully', async () => {
|
||||
const mockConfigResponse = {
|
||||
configured: false,
|
||||
message: 'No configuration found',
|
||||
};
|
||||
|
||||
const mockCreateResponse = {
|
||||
status: 'Configuration created successfully',
|
||||
};
|
||||
|
||||
const newConfig = {
|
||||
server: { host: '0.0.0.0', port: 5055 },
|
||||
cloud: {
|
||||
domain: 'wildcloud.local',
|
||||
internalDomain: 'cluster.local',
|
||||
dhcpRange: '192.168.8.100,192.168.8.200',
|
||||
dns: { ip: '192.168.8.50' },
|
||||
router: { ip: '192.168.8.1' },
|
||||
dnsmasq: { interface: 'eth0' },
|
||||
},
|
||||
cluster: {
|
||||
endpointIp: '192.168.8.60',
|
||||
nodes: { talos: { version: 'v1.8.0' } },
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(apiService.getConfig).mockResolvedValue(mockConfigResponse);
|
||||
vi.mocked(apiService.createConfig).mockResolvedValue(mockCreateResponse);
|
||||
|
||||
const { result } = renderHook(() => useConfig(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.showConfigSetup).toBe(true);
|
||||
|
||||
// Create config
|
||||
await act(async () => {
|
||||
result.current.createConfig(newConfig);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isCreating).toBe(false);
|
||||
});
|
||||
|
||||
expect(apiService.createConfig).toHaveBeenCalledWith(newConfig);
|
||||
});
|
||||
|
||||
it('should handle error when fetching config fails', async () => {
|
||||
const mockError = new Error('Network error');
|
||||
vi.mocked(apiService.getConfig).mockRejectedValue(mockError);
|
||||
|
||||
const { result } = renderHook(() => useConfig(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual(mockError);
|
||||
expect(result.current.config).toBeNull();
|
||||
});
|
||||
});
|
||||
127
experimental/app/src/hooks/__tests__/useMessages.test.ts
Normal file
127
experimental/app/src/hooks/__tests__/useMessages.test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useMessages } from '../useMessages';
|
||||
|
||||
describe('useMessages', () => {
|
||||
it('should initialize with empty messages', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
expect(result.current.messages).toEqual({});
|
||||
});
|
||||
|
||||
it('should set a message', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test', 'Test message', 'success');
|
||||
});
|
||||
|
||||
expect(result.current.messages).toEqual({
|
||||
test: { message: 'Test message', type: 'success' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should set multiple messages', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('success', 'Success message', 'success');
|
||||
result.current.setMessage('error', 'Error message', 'error');
|
||||
result.current.setMessage('info', 'Info message', 'info');
|
||||
});
|
||||
|
||||
expect(result.current.messages).toEqual({
|
||||
success: { message: 'Success message', type: 'success' },
|
||||
error: { message: 'Error message', type: 'error' },
|
||||
info: { message: 'Info message', type: 'info' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should update existing message', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test', 'First message', 'info');
|
||||
});
|
||||
|
||||
expect(result.current.messages.test).toEqual({
|
||||
message: 'First message',
|
||||
type: 'info'
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test', 'Updated message', 'error');
|
||||
});
|
||||
|
||||
expect(result.current.messages.test).toEqual({
|
||||
message: 'Updated message',
|
||||
type: 'error'
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear a specific message', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test1', 'Message 1', 'info');
|
||||
result.current.setMessage('test2', 'Message 2', 'success');
|
||||
});
|
||||
|
||||
expect(Object.keys(result.current.messages)).toHaveLength(2);
|
||||
|
||||
act(() => {
|
||||
result.current.clearMessage('test1');
|
||||
});
|
||||
|
||||
expect(result.current.messages).toEqual({
|
||||
test2: { message: 'Message 2', type: 'success' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear message by setting to null', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test', 'Test message', 'info');
|
||||
});
|
||||
|
||||
expect(result.current.messages.test).toBeDefined();
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test', null);
|
||||
});
|
||||
|
||||
expect(result.current.messages.test).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should clear all messages', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test1', 'Message 1', 'info');
|
||||
result.current.setMessage('test2', 'Message 2', 'success');
|
||||
result.current.setMessage('test3', 'Message 3', 'error');
|
||||
});
|
||||
|
||||
expect(Object.keys(result.current.messages)).toHaveLength(3);
|
||||
|
||||
act(() => {
|
||||
result.current.clearAllMessages();
|
||||
});
|
||||
|
||||
expect(result.current.messages).toEqual({});
|
||||
});
|
||||
|
||||
it('should default to info type when type not specified', () => {
|
||||
const { result } = renderHook(() => useMessages());
|
||||
|
||||
act(() => {
|
||||
result.current.setMessage('test', 'Test message');
|
||||
});
|
||||
|
||||
expect(result.current.messages.test).toEqual({
|
||||
message: 'Test message',
|
||||
type: 'info'
|
||||
});
|
||||
});
|
||||
});
|
||||
104
experimental/app/src/hooks/__tests__/useStatus.test.ts
Normal file
104
experimental/app/src/hooks/__tests__/useStatus.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import { useStatus } from '../useStatus';
|
||||
import { apiService } from '../../services/api';
|
||||
|
||||
// Mock the API service
|
||||
vi.mock('../../services/api', () => ({
|
||||
apiService: {
|
||||
getStatus: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
gcTime: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children)
|
||||
);
|
||||
};
|
||||
|
||||
describe('useStatus', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should fetch status successfully', async () => {
|
||||
const mockStatus = {
|
||||
status: 'running',
|
||||
version: '1.0.0',
|
||||
uptime: '2 hours',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
vi.mocked(apiService.getStatus).mockResolvedValue(mockStatus);
|
||||
|
||||
const { result } = renderHook(() => useStatus(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(mockStatus);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(apiService.getStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle error when fetching status fails', async () => {
|
||||
const mockError = new Error('Network error');
|
||||
vi.mocked(apiService.getStatus).mockRejectedValue(mockError);
|
||||
|
||||
const { result } = renderHook(() => useStatus(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.data).toBeUndefined();
|
||||
expect(result.current.error).toEqual(mockError);
|
||||
});
|
||||
|
||||
it('should refetch data when refetch is called', async () => {
|
||||
const mockStatus = {
|
||||
status: 'running',
|
||||
version: '1.0.0',
|
||||
uptime: '2 hours',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
vi.mocked(apiService.getStatus).mockResolvedValue(mockStatus);
|
||||
|
||||
const { result } = renderHook(() => useStatus(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(apiService.getStatus).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Trigger refetch
|
||||
result.current.refetch();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiService.getStatus).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
7
experimental/app/src/hooks/index.ts
Normal file
7
experimental/app/src/hooks/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { useMessages } from './useMessages';
|
||||
export { useStatus } from './useStatus';
|
||||
export { useHealth } from './useHealth';
|
||||
export { useConfig } from './useConfig';
|
||||
export { useConfigYaml } from './useConfigYaml';
|
||||
export { useDnsmasq } from './useDnsmasq';
|
||||
export { useAssets } from './useAssets';
|
||||
19
experimental/app/src/hooks/use-mobile.ts
Normal file
19
experimental/app/src/hooks/use-mobile.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
19
experimental/app/src/hooks/useAssets.ts
Normal file
19
experimental/app/src/hooks/useAssets.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { apiService } from '../services/api';
|
||||
|
||||
interface AssetsResponse {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const useAssets = () => {
|
||||
const downloadMutation = useMutation<AssetsResponse>({
|
||||
mutationFn: apiService.downloadPXEAssets,
|
||||
});
|
||||
|
||||
return {
|
||||
downloadAssets: downloadMutation.mutate,
|
||||
isDownloading: downloadMutation.isPending,
|
||||
error: downloadMutation.error,
|
||||
data: downloadMutation.data,
|
||||
};
|
||||
};
|
||||
52
experimental/app/src/hooks/useConfig.ts
Normal file
52
experimental/app/src/hooks/useConfig.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiService } from '../services/api';
|
||||
import type { Config } from '../types';
|
||||
|
||||
interface ConfigResponse {
|
||||
configured: boolean;
|
||||
config?: Config;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface CreateConfigResponse {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const useConfig = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const [showConfigSetup, setShowConfigSetup] = useState(false);
|
||||
|
||||
const configQuery = useQuery<ConfigResponse>({
|
||||
queryKey: ['config'],
|
||||
queryFn: () => apiService.getConfig(),
|
||||
});
|
||||
|
||||
// Update showConfigSetup based on query data
|
||||
useEffect(() => {
|
||||
if (configQuery.data) {
|
||||
setShowConfigSetup(configQuery.data.configured === false);
|
||||
}
|
||||
}, [configQuery.data]);
|
||||
|
||||
const createConfigMutation = useMutation<CreateConfigResponse, Error, Config>({
|
||||
mutationFn: apiService.createConfig,
|
||||
onSuccess: () => {
|
||||
// Invalidate and refetch config after successful creation
|
||||
queryClient.invalidateQueries({ queryKey: ['config'] });
|
||||
setShowConfigSetup(false);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
config: configQuery.data?.config || null,
|
||||
isConfigured: configQuery.data?.configured || false,
|
||||
showConfigSetup,
|
||||
setShowConfigSetup,
|
||||
isLoading: configQuery.isLoading,
|
||||
isCreating: createConfigMutation.isPending,
|
||||
error: configQuery.error || createConfigMutation.error,
|
||||
createConfig: createConfigMutation.mutate,
|
||||
refetch: configQuery.refetch,
|
||||
};
|
||||
};
|
||||
40
experimental/app/src/hooks/useConfigYaml.ts
Normal file
40
experimental/app/src/hooks/useConfigYaml.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiService } from '../services/api';
|
||||
|
||||
export const useConfigYaml = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const configYamlQuery = useQuery({
|
||||
queryKey: ['config', 'yaml'],
|
||||
queryFn: () => apiService.getConfigYaml(),
|
||||
staleTime: 30000, // Consider data fresh for 30 seconds
|
||||
retry: true,
|
||||
});
|
||||
|
||||
const updateConfigYamlMutation = useMutation({
|
||||
mutationFn: (data: string) => apiService.updateConfigYaml(data),
|
||||
onSuccess: () => {
|
||||
// Invalidate both YAML and JSON config queries
|
||||
queryClient.invalidateQueries({ queryKey: ['config'] });
|
||||
},
|
||||
});
|
||||
|
||||
// Check if error is 404 (endpoint doesn't exist)
|
||||
const isEndpointMissing = configYamlQuery.error &&
|
||||
configYamlQuery.error instanceof Error &&
|
||||
configYamlQuery.error.message.includes('404');
|
||||
|
||||
// Only pass through real errors
|
||||
const actualError = (configYamlQuery.error instanceof Error ? configYamlQuery.error : null) ||
|
||||
(updateConfigYamlMutation.error instanceof Error ? updateConfigYamlMutation.error : null);
|
||||
|
||||
return {
|
||||
yamlContent: configYamlQuery.data || '',
|
||||
isLoading: configYamlQuery.isLoading,
|
||||
error: actualError,
|
||||
isEndpointMissing,
|
||||
isUpdating: updateConfigYamlMutation.isPending,
|
||||
updateYaml: updateConfigYamlMutation.mutate,
|
||||
refetch: configYamlQuery.refetch,
|
||||
};
|
||||
};
|
||||
33
experimental/app/src/hooks/useDnsmasq.ts
Normal file
33
experimental/app/src/hooks/useDnsmasq.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useState } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { apiService } from '../services/api';
|
||||
|
||||
interface DnsmasqResponse {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const useDnsmasq = () => {
|
||||
const [dnsmasqConfig, setDnsmasqConfig] = useState<string>('');
|
||||
|
||||
const generateConfigMutation = useMutation<string>({
|
||||
mutationFn: apiService.getDnsmasqConfig,
|
||||
onSuccess: (data) => {
|
||||
setDnsmasqConfig(data);
|
||||
},
|
||||
});
|
||||
|
||||
const restartMutation = useMutation<DnsmasqResponse>({
|
||||
mutationFn: apiService.restartDnsmasq,
|
||||
});
|
||||
|
||||
return {
|
||||
dnsmasqConfig,
|
||||
generateConfig: generateConfigMutation.mutate,
|
||||
isGenerating: generateConfigMutation.isPending,
|
||||
generateError: generateConfigMutation.error,
|
||||
restart: restartMutation.mutate,
|
||||
isRestarting: restartMutation.isPending,
|
||||
restartError: restartMutation.error,
|
||||
restartData: restartMutation.data,
|
||||
};
|
||||
};
|
||||
13
experimental/app/src/hooks/useHealth.ts
Normal file
13
experimental/app/src/hooks/useHealth.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { apiService } from '../services/api';
|
||||
|
||||
interface HealthResponse {
|
||||
service: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const useHealth = () => {
|
||||
return useMutation<HealthResponse>({
|
||||
mutationFn: apiService.getHealth,
|
||||
});
|
||||
};
|
||||
33
experimental/app/src/hooks/useMessages.ts
Normal file
33
experimental/app/src/hooks/useMessages.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useState } from 'react';
|
||||
import type { Messages } from '../types';
|
||||
|
||||
export const useMessages = () => {
|
||||
const [messages, setMessages] = useState<Messages>({});
|
||||
|
||||
const setMessage = (key: string, message: string | null, type: 'info' | 'success' | 'error' = 'info') => {
|
||||
if (message === null) {
|
||||
setMessages(prev => {
|
||||
const newMessages = { ...prev };
|
||||
delete newMessages[key];
|
||||
return newMessages;
|
||||
});
|
||||
} else {
|
||||
setMessages(prev => ({ ...prev, [key]: { message, type } }));
|
||||
}
|
||||
};
|
||||
|
||||
const clearMessage = (key: string) => {
|
||||
setMessage(key, null);
|
||||
};
|
||||
|
||||
const clearAllMessages = () => {
|
||||
setMessages({});
|
||||
};
|
||||
|
||||
return {
|
||||
messages,
|
||||
setMessage,
|
||||
clearMessage,
|
||||
clearAllMessages,
|
||||
};
|
||||
};
|
||||
11
experimental/app/src/hooks/useStatus.ts
Normal file
11
experimental/app/src/hooks/useStatus.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiService } from '../services/api';
|
||||
import type { Status } from '../types';
|
||||
|
||||
export const useStatus = () => {
|
||||
return useQuery<Status>({
|
||||
queryKey: ['status'],
|
||||
queryFn: apiService.getStatus,
|
||||
refetchInterval: 30000, // Refetch every 30 seconds
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user