first commit
Some checks failed
Deploy / lint (push) Failing after 7s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped

This commit is contained in:
Dennis Thiessen
2026-02-20 17:31:01 +01:00
commit 61ab24490d
160 changed files with 17034 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as adminApi from '../api/admin';
import { useToast } from '../components/ui/Toast';
// ── Users ──
export function useUsers() {
return useQuery({
queryKey: ['admin', 'users'],
queryFn: () => adminApi.listUsers(),
});
}
export function useCreateUser() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (data: {
username: string;
password: string;
role: string;
has_access: boolean;
}) => adminApi.createUser(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'users'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to create user');
},
});
}
export function useUpdateAccess() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: ({ userId, hasAccess }: { userId: number; hasAccess: boolean }) =>
adminApi.updateAccess(userId, hasAccess),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'users'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to update access');
},
});
}
export function useResetPassword() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: ({ userId, password }: { userId: number; password: string }) =>
adminApi.resetPassword(userId, password),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'users'] });
addToast('success', 'Password reset successfully');
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to reset password');
},
});
}
// ── Settings ──
export function useSettings() {
return useQuery({
queryKey: ['admin', 'settings'],
queryFn: () => adminApi.listSettings(),
});
}
export function useUpdateSetting() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: ({ key, value }: { key: string; value: string }) =>
adminApi.updateSetting(key, value),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'settings'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to update setting');
},
});
}
// ── Jobs ──
export function useJobs() {
return useQuery({
queryKey: ['admin', 'jobs'],
queryFn: () => adminApi.listJobs(),
refetchInterval: 15_000,
});
}
export function useToggleJob() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: ({ jobName, enabled }: { jobName: string; enabled: boolean }) =>
adminApi.toggleJob(jobName, enabled),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'jobs'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to toggle job');
},
});
}
export function useTriggerJob() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (jobName: string) => adminApi.triggerJob(jobName),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'jobs'] });
addToast('success', 'Job triggered successfully');
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to trigger job');
},
});
}
// ── Data Cleanup ──
export function useCleanupData() {
const { addToast } = useToast();
return useMutation({
mutationFn: (olderThanDays: number) => adminApi.cleanupData(olderThanDays),
onSuccess: (data) => {
addToast('success', (data as { message: string }).message || 'Cleanup completed');
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to cleanup data');
},
});
}

View File

@@ -0,0 +1,22 @@
import { useMutation } from '@tanstack/react-query';
import * as authApi from '../api/auth';
import { useAuthStore } from '../stores/authStore';
export function useLogin() {
const storeLogin = useAuthStore((s) => s.login);
return useMutation({
mutationFn: ({ username, password }: { username: string; password: string }) =>
authApi.login(username, password),
onSuccess: (data) => {
storeLogin(data.access_token);
},
});
}
export function useRegister() {
return useMutation({
mutationFn: ({ username, password }: { username: string; password: string }) =>
authApi.register(username, password),
});
}

View File

@@ -0,0 +1,26 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as scoresApi from '../api/scores';
import { useToast } from '../components/ui/Toast';
export function useRankings() {
return useQuery({
queryKey: ['rankings'],
queryFn: () => scoresApi.getRankings(),
});
}
export function useUpdateWeights() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (weights: Record<string, number>) => scoresApi.updateWeights(weights),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['rankings'] });
addToast('success', 'Weights updated successfully');
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to update weights');
},
});
}

View File

@@ -0,0 +1,40 @@
import { useQuery } from '@tanstack/react-query';
import { getOHLCV } from '../api/ohlcv';
import { getScores } from '../api/scores';
import { getLevels } from '../api/sr-levels';
import { getSentiment } from '../api/sentiment';
import { getFundamentals } from '../api/fundamentals';
export function useTickerDetail(symbol: string) {
const ohlcv = useQuery({
queryKey: ['ohlcv', symbol],
queryFn: () => getOHLCV(symbol),
enabled: !!symbol,
});
const scores = useQuery({
queryKey: ['scores', symbol],
queryFn: () => getScores(symbol),
enabled: !!symbol,
});
const srLevels = useQuery({
queryKey: ['sr-levels', symbol],
queryFn: () => getLevels(symbol),
enabled: !!symbol,
});
const sentiment = useQuery({
queryKey: ['sentiment', symbol],
queryFn: () => getSentiment(symbol),
enabled: !!symbol,
});
const fundamentals = useQuery({
queryKey: ['fundamentals', symbol],
queryFn: () => getFundamentals(symbol),
enabled: !!symbol,
});
return { ohlcv, scores, srLevels, sentiment, fundamentals };
}

View File

@@ -0,0 +1,40 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as tickersApi from '../api/tickers';
import { useToast } from '../components/ui/Toast';
export function useTickers() {
return useQuery({
queryKey: ['tickers'],
queryFn: () => tickersApi.list(),
});
}
export function useAddTicker() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (symbol: string) => tickersApi.create(symbol),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['tickers'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to add ticker');
},
});
}
export function useDeleteTicker() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (symbol: string) => tickersApi.deleteTicker(symbol),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['tickers'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to delete ticker');
},
});
}

View File

@@ -0,0 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import * as tradesApi from '../api/trades';
export function useTrades() {
return useQuery({
queryKey: ['trades'],
queryFn: () => tradesApi.list(),
});
}

View File

@@ -0,0 +1,40 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as watchlistApi from '../api/watchlist';
import { useToast } from '../components/ui/Toast';
export function useWatchlist() {
return useQuery({
queryKey: ['watchlist'],
queryFn: () => watchlistApi.list(),
});
}
export function useAddToWatchlist() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (symbol: string) => watchlistApi.add(symbol),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['watchlist'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to add to watchlist');
},
});
}
export function useRemoveFromWatchlist() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (symbol: string) => watchlistApi.remove(symbol),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['watchlist'] });
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to remove from watchlist');
},
});
}