Replace custom useApi/useAgents hooks with @tanstack/react-query. Add LiveUpdatesProvider for WebSocket-driven cache invalidation. Add queryKeys module for centralized cache key management. Rework all pages and dialogs to use React Query mutations and queries. Improve CompanyContext with query-based data fetching. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import type { Company } from "@paperclip/shared";
|
|
import { companiesApi } from "../api/companies";
|
|
import { queryKeys } from "../lib/queryKeys";
|
|
|
|
interface CompanyContextValue {
|
|
companies: Company[];
|
|
selectedCompanyId: string | null;
|
|
selectedCompany: Company | null;
|
|
loading: boolean;
|
|
error: Error | null;
|
|
setSelectedCompanyId: (companyId: string) => void;
|
|
reloadCompanies: () => Promise<void>;
|
|
createCompany: (data: {
|
|
name: string;
|
|
description?: string | null;
|
|
budgetMonthlyCents?: number;
|
|
}) => Promise<Company>;
|
|
}
|
|
|
|
const STORAGE_KEY = "paperclip.selectedCompanyId";
|
|
|
|
const CompanyContext = createContext<CompanyContextValue | null>(null);
|
|
|
|
export function CompanyProvider({ children }: { children: ReactNode }) {
|
|
const queryClient = useQueryClient();
|
|
const [selectedCompanyId, setSelectedCompanyIdState] = useState<string | null>(
|
|
() => localStorage.getItem(STORAGE_KEY)
|
|
);
|
|
|
|
const { data: companies = [], isLoading, error } = useQuery({
|
|
queryKey: queryKeys.companies.all,
|
|
queryFn: () => companiesApi.list(),
|
|
});
|
|
|
|
// Auto-select first company when list loads
|
|
useEffect(() => {
|
|
if (companies.length === 0) return;
|
|
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (stored && companies.some((c) => c.id === stored)) return;
|
|
if (selectedCompanyId && companies.some((c) => c.id === selectedCompanyId)) return;
|
|
|
|
const next = companies[0]!.id;
|
|
setSelectedCompanyIdState(next);
|
|
localStorage.setItem(STORAGE_KEY, next);
|
|
}, [companies, selectedCompanyId]);
|
|
|
|
const setSelectedCompanyId = useCallback((companyId: string) => {
|
|
setSelectedCompanyIdState(companyId);
|
|
localStorage.setItem(STORAGE_KEY, companyId);
|
|
}, []);
|
|
|
|
const reloadCompanies = useCallback(async () => {
|
|
await queryClient.invalidateQueries({ queryKey: queryKeys.companies.all });
|
|
}, [queryClient]);
|
|
|
|
const createMutation = useMutation({
|
|
mutationFn: (data: { name: string; description?: string | null; budgetMonthlyCents?: number }) =>
|
|
companiesApi.create(data),
|
|
onSuccess: (company) => {
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.companies.all });
|
|
setSelectedCompanyId(company.id);
|
|
},
|
|
});
|
|
|
|
const createCompany = useCallback(
|
|
async (data: { name: string; description?: string | null; budgetMonthlyCents?: number }) => {
|
|
return createMutation.mutateAsync(data);
|
|
},
|
|
[createMutation],
|
|
);
|
|
|
|
const selectedCompany = useMemo(
|
|
() => companies.find((company) => company.id === selectedCompanyId) ?? null,
|
|
[companies, selectedCompanyId],
|
|
);
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
companies,
|
|
selectedCompanyId,
|
|
selectedCompany,
|
|
loading: isLoading,
|
|
error: error as Error | null,
|
|
setSelectedCompanyId,
|
|
reloadCompanies,
|
|
createCompany,
|
|
}),
|
|
[
|
|
companies,
|
|
selectedCompanyId,
|
|
selectedCompany,
|
|
isLoading,
|
|
error,
|
|
setSelectedCompanyId,
|
|
reloadCompanies,
|
|
createCompany,
|
|
],
|
|
);
|
|
|
|
return <CompanyContext.Provider value={value}>{children}</CompanyContext.Provider>;
|
|
}
|
|
|
|
export function useCompany() {
|
|
const ctx = useContext(CompanyContext);
|
|
if (!ctx) {
|
|
throw new Error("useCompany must be used within CompanyProvider");
|
|
}
|
|
return ctx;
|
|
}
|