Files
paperclip/ui/src/context/CompanyContext.tsx
Forgotten 3dc3813266 Adopt React Query and live updates across all UI pages
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>
2026-02-17 12:24:48 -06:00

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;
}