Initial commit: CI Agent competitive-intelligence monitoring app
FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import type { AlertListFilters, AlertUpdatePayload } from "@/lib/types";
|
||||
|
||||
export function useAlerts(filters: AlertListFilters = {}) {
|
||||
return useQuery({
|
||||
queryKey: ["alerts", filters],
|
||||
queryFn: () => api.listAlerts(filters),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAlert(alertId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ["alerts", "detail", alertId],
|
||||
queryFn: () => api.getAlert(alertId as string),
|
||||
enabled: Boolean(alertId),
|
||||
});
|
||||
}
|
||||
|
||||
function useInvalidateAlerts() {
|
||||
const queryClient = useQueryClient();
|
||||
return () => queryClient.invalidateQueries({ queryKey: ["alerts"] });
|
||||
}
|
||||
|
||||
export function useUpdateAlert(alertId: string) {
|
||||
const invalidate = useInvalidateAlerts();
|
||||
return useMutation({
|
||||
mutationFn: (payload: AlertUpdatePayload) => api.updateAlert(alertId, payload),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useMarkAlertRead() {
|
||||
const invalidate = useInvalidateAlerts();
|
||||
return useMutation({
|
||||
mutationFn: (alertId: string) => api.markAlertRead(alertId),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useResolveAlert() {
|
||||
const invalidate = useInvalidateAlerts();
|
||||
return useMutation({
|
||||
mutationFn: (alertId: string) => api.resolveAlert(alertId),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
|
||||
export function useDashboardAnalytics() {
|
||||
return useQuery({
|
||||
queryKey: ["dashboard-analytics"],
|
||||
queryFn: api.getDashboardAnalytics,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ApiError, api, clearTokens, getRefreshToken, setTokens } from "@/lib/api-client";
|
||||
import type {
|
||||
BanIpPayload,
|
||||
ConfirmPasswordResetPayload,
|
||||
LoginPayload,
|
||||
RegisterPayload,
|
||||
RequestPasswordResetPayload,
|
||||
ResendVerificationPayload,
|
||||
SetSystemSecretPayload,
|
||||
SetUserApiKeyPayload,
|
||||
SystemSecretStatus,
|
||||
UnbanRequestPayload,
|
||||
UserApiKeyStatus,
|
||||
VerifyEmailPayload,
|
||||
} from "@/lib/types";
|
||||
|
||||
export function useSystemStatus() {
|
||||
return useQuery({
|
||||
queryKey: ["system-status"],
|
||||
queryFn: api.systemStatus,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemSecrets() {
|
||||
return useQuery({
|
||||
queryKey: ["system-secrets"],
|
||||
queryFn: api.listSystemSecrets,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSetSystemSecret() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ key, payload }: { key: string; payload: SetSystemSecretPayload }) =>
|
||||
api.setSystemSecret(key, payload),
|
||||
onSuccess: (updated) => {
|
||||
queryClient.setQueryData<SystemSecretStatus[]>(["system-secrets"], (prev) =>
|
||||
prev?.map((s) => (s.key === updated.key ? updated : s)),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ["system-status"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSystemLogs() {
|
||||
return useQuery({
|
||||
queryKey: ["system-logs"],
|
||||
queryFn: api.systemLogs,
|
||||
refetchInterval: 5_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCurrentUser() {
|
||||
return useQuery({
|
||||
queryKey: ["me"],
|
||||
queryFn: api.me,
|
||||
retry: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLogin() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: LoginPayload) => api.login(payload),
|
||||
onSuccess: (tokens) => {
|
||||
setTokens(tokens.access_token, tokens.refresh_token);
|
||||
return queryClient.invalidateQueries({ queryKey: ["me"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRegister() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: RegisterPayload) => api.register(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function useLogout() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const refreshToken = getRefreshToken();
|
||||
if (refreshToken) {
|
||||
await api.logout(refreshToken).catch(() => undefined);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
clearTokens();
|
||||
// We already know definitively the user is logged out here - clear
|
||||
// the whole cache and navigate immediately, rather than invalidating
|
||||
// and waiting for a background refetch of ["me"] to fail with 401.
|
||||
// That indirection left a window where the dashboard kept rendering
|
||||
// stale cached data until the reactive layout redirect eventually
|
||||
// caught up, flickering between dashboard and login on every logout.
|
||||
queryClient.clear();
|
||||
router.replace("/login");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useVerifyEmail() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: VerifyEmailPayload) => api.verifyEmail(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function useResendVerification() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: ResendVerificationPayload) => api.resendVerification(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function useRequestPasswordReset() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: RequestPasswordResetPayload) => api.requestPasswordReset(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function useConfirmPasswordReset() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: ConfirmPasswordResetPayload) => api.confirmPasswordReset(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSecurityEvents() {
|
||||
return useQuery({
|
||||
queryKey: ["security-events"],
|
||||
queryFn: api.securityEvents,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSubmitUnbanRequest() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: UnbanRequestPayload) => api.submitUnbanRequest(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function useIpBans() {
|
||||
return useQuery({
|
||||
queryKey: ["ip-bans"],
|
||||
queryFn: api.listIpBans,
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnbanRequests() {
|
||||
return useQuery({
|
||||
queryKey: ["unban-requests"],
|
||||
queryFn: api.listUnbanRequests,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteIpBan() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (ipAddress: string) => api.deleteIpBan(ipAddress),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["ip-bans"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateIpBan() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: BanIpPayload) => api.createIpBan(payload),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["ip-bans"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAcceptUnbanRequest() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (requestId: string) => api.acceptUnbanRequest(requestId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["ip-bans"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["unban-requests"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRejectUnbanRequest() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (requestId: string) => api.rejectUnbanRequest(requestId),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["unban-requests"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUserApiKeys() {
|
||||
return useQuery({
|
||||
queryKey: ["user-api-keys"],
|
||||
queryFn: api.listUserApiKeys,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSetUserApiKey() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ provider, payload }: { provider: string; payload: SetUserApiKeyPayload }) =>
|
||||
api.setUserApiKey(provider, payload),
|
||||
onSuccess: (updated) => {
|
||||
queryClient.setQueryData<UserApiKeyStatus[]>(["user-api-keys"], (prev) =>
|
||||
prev?.map((k) => (k.provider === updated.provider ? updated : k)),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Seconds until a throttled action (resend/reset) can be retried, or null
|
||||
* if the most recent error wasn't a throttle response - drives a live
|
||||
* countdown on the resend button. */
|
||||
export function retryAfterSeconds(error: unknown): number | null {
|
||||
if (error instanceof ApiError && typeof error.body?.retry_after_seconds === "number") {
|
||||
return error.body.retry_after_seconds;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export type LoginFailureKind =
|
||||
| "verify_email"
|
||||
| "account_locked"
|
||||
| "throttled"
|
||||
| "banned"
|
||||
| "generic";
|
||||
|
||||
export interface LoginFailure {
|
||||
kind: LoginFailureKind;
|
||||
message: string;
|
||||
retryAfterSeconds: number | null;
|
||||
}
|
||||
|
||||
/** The backend keeps login error messages generic on purpose (never
|
||||
* confirms account existence), so the UI distinguishes cases by status code
|
||||
* and message shape rather than a structured error code. */
|
||||
export function classifyLoginError(error: unknown): LoginFailure {
|
||||
const message = authErrorMessage(error);
|
||||
if (error instanceof ApiError && error.status === 429) {
|
||||
const retry = retryAfterSeconds(error);
|
||||
return { kind: retry !== null ? "throttled" : "banned", message, retryAfterSeconds: retry };
|
||||
}
|
||||
if (/verify your email/i.test(message)) {
|
||||
return { kind: "verify_email", message, retryAfterSeconds: null };
|
||||
}
|
||||
if (/account locked/i.test(message)) {
|
||||
return { kind: "account_locked", message, retryAfterSeconds: null };
|
||||
}
|
||||
return { kind: "generic", message, retryAfterSeconds: null };
|
||||
}
|
||||
|
||||
export function authErrorMessage(error: unknown): string {
|
||||
if (error instanceof ApiError) {
|
||||
if (error.body?.detail) return error.body.detail;
|
||||
if (error.body?.errors?.length) {
|
||||
return error.body.errors
|
||||
.map((e) => e.msg)
|
||||
.filter(Boolean)
|
||||
.join(", ");
|
||||
}
|
||||
}
|
||||
return "Something went wrong. Please try again.";
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import type {
|
||||
CompanyCreatePayload,
|
||||
CompanyResponse,
|
||||
CompanyUpdatePayload,
|
||||
MonitorConfigurationUpdatePayload,
|
||||
} from "@/lib/types";
|
||||
|
||||
export function useCompanies() {
|
||||
return useQuery({ queryKey: ["companies"], queryFn: api.listCompanies });
|
||||
}
|
||||
|
||||
export function useCompany(companyId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ["companies", companyId],
|
||||
queryFn: () => api.getCompany(companyId as string),
|
||||
enabled: Boolean(companyId),
|
||||
// Enrichment (NinjaPear) runs in the background after company
|
||||
// creation - poll while it's still pending so the Enrichment tab
|
||||
// updates itself once the task finishes, without a manual refresh.
|
||||
refetchInterval: (query) => {
|
||||
const company = query.state.data as CompanyResponse | undefined;
|
||||
return company?.enrichment?.status === "pending" ? 3000 : false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateCompany() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: CompanyCreatePayload) => api.createCompany(payload),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["companies"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateCompany(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: CompanyUpdatePayload) => api.updateCompany(companyId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["companies"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["companies", companyId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteCompany() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (companyId: string) => api.deleteCompany(companyId),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["companies"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSetCompanyPaused() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ companyId, paused }: { companyId: string; paused: boolean }) =>
|
||||
paused ? api.pauseCompany(companyId) : api.resumeCompany(companyId),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["companies"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["companies", variables.companyId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateMonitorConfiguration(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: MonitorConfigurationUpdatePayload) =>
|
||||
api.updateMonitorConfiguration(companyId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["companies"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["companies", companyId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/** A countdown in whole seconds, started on demand via `start(seconds)` -
|
||||
* drives the "Resend in Ns" button state after a 429 throttle response. */
|
||||
export function useCountdown() {
|
||||
const [remaining, setRemaining] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (remaining <= 0) return;
|
||||
const timer = setTimeout(() => setRemaining((s) => Math.max(0, s - 1)), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [remaining]);
|
||||
|
||||
return { remaining, start: (seconds: number) => setRemaining(seconds) };
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import type { DiscoverCompanyRequest } from "@/lib/types";
|
||||
|
||||
export function useDiscoverCompany() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: DiscoverCompanyRequest) => api.discoverCompany(payload),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import type { MonitoringRunResponse } from "@/lib/types";
|
||||
|
||||
const ACTIVE_STATUSES = new Set(["queued", "running"]);
|
||||
|
||||
export function useCompanyRuns(companyId: string) {
|
||||
return useQuery({
|
||||
queryKey: ["runs", companyId],
|
||||
queryFn: () => api.listCompanyRuns(companyId),
|
||||
refetchInterval: (query) => {
|
||||
const runs = query.state.data as MonitoringRunResponse[] | undefined;
|
||||
return runs?.some((r) => ACTIVE_STATUSES.has(r.status)) ? 2000 : false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRunCompanyNow(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: () => api.runCompanyNow(companyId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["runs", companyId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["companies", companyId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["sources", companyId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Same action, for call sites (like the companies list) that don't want to
|
||||
* fix a single companyId at the hook-call site. */
|
||||
export function useRunAnyCompanyNow() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (companyId: string) => api.runCompanyNow(companyId),
|
||||
onSuccess: (_data, companyId) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["runs", companyId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["companies"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import type {
|
||||
NotificationDestinationCreatePayload,
|
||||
NotificationDestinationUpdatePayload,
|
||||
} from "@/lib/types";
|
||||
|
||||
export function useNotificationDestinations() {
|
||||
return useQuery({
|
||||
queryKey: ["notification-destinations"],
|
||||
queryFn: api.listNotificationDestinations,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateNotificationDestination() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: NotificationDestinationCreatePayload) =>
|
||||
api.createNotificationDestination(payload),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["notification-destinations"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateNotificationDestination() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
destinationId,
|
||||
payload,
|
||||
}: {
|
||||
destinationId: string;
|
||||
payload: NotificationDestinationUpdatePayload;
|
||||
}) => api.updateNotificationDestination(destinationId, payload),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["notification-destinations"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteNotificationDestination() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (destinationId: string) => api.deleteNotificationDestination(destinationId),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["notification-destinations"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnlinkNotificationDestinationCompany() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ destinationId, companyId }: { destinationId: string; companyId: string }) =>
|
||||
api.unlinkNotificationDestinationCompany(destinationId, companyId),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["notification-destinations"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useTestNotificationDestination() {
|
||||
return useMutation({
|
||||
mutationFn: (destinationId: string) => api.testNotificationDestination(destinationId),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
|
||||
export function useCompanyReports(companyId: string) {
|
||||
return useQuery({ queryKey: ["reports", companyId], queryFn: () => api.listReports(companyId) });
|
||||
}
|
||||
|
||||
export function useReport(reportId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ["report", reportId],
|
||||
queryFn: () => api.getReport(reportId as string),
|
||||
enabled: Boolean(reportId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useReportMarkdown(reportId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ["report", reportId, "markdown"],
|
||||
queryFn: () => api.getReportMarkdown(reportId as string),
|
||||
enabled: Boolean(reportId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useGenerateReport(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: () => api.generateReport(companyId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["reports", companyId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["companies", companyId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
|
||||
export function useSnapshots(companyId: string) {
|
||||
return useQuery({
|
||||
queryKey: ["snapshots", companyId],
|
||||
queryFn: () => api.listSnapshots(companyId),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import type { SourceCreatePayload, SourceUpdatePayload } from "@/lib/types";
|
||||
|
||||
export function useSources(companyId: string) {
|
||||
return useQuery({ queryKey: ["sources", companyId], queryFn: () => api.listSources(companyId) });
|
||||
}
|
||||
|
||||
export function useCreateSource(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: SourceCreatePayload) => api.createSource(companyId, payload),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["sources", companyId] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateSource(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ sourceId, payload }: { sourceId: string; payload: SourceUpdatePayload }) =>
|
||||
api.updateSource(sourceId, payload),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["sources", companyId] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSource(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (sourceId: string) => api.deleteSource(sourceId),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["sources", companyId] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useTestSource(companyId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (sourceId: string) => api.testSource(sourceId),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["sources", companyId] }),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user