"use client"; import { createContext, use, useCallback, useMemo, useState, type ReactNode } from "react"; import { useToast } from "@unlocalhosted/metalui"; import { createLabelAction } from "@/app/actions/issues"; import { LABEL_COLORS, type IssueLabel, type Member } from "@/lib/issue-meta"; type Tone = "info" | "success" | "error"; type Tracker = { owner: string; name: string; base: string; members: Member[]; labels: IssueLabel[]; viewerId: string | null; /** Push access: may set priority, assignee, labels, estimates and reorder. */ canTriage: boolean; /** Today as YYYY-MM-DD, from the server, for overdue highlighting. */ today: string; createLabel: (name: string) => Promise; /** The result of the person's own action, on MetalUI's toast deck (errors stay until dismissed). */ notify: (message: string, options?: { sub?: string; tone?: Tone }) => void; }; const TrackerContext = createContext(null); export function useTracker() { const tracker = use(TrackerContext); if (!tracker) throw new Error("useTracker must be used inside "); return tracker; } export function TrackerProvider({ children, labels: serverLabels, ...rest }: Omit & { children: ReactNode }) { const { owner, name } = rest; const toast = useToast(); const [created, setCreated] = useState([]); const notify = useCallback( (message, options) => void toast.show({ title: message, sub: options?.sub, tone: options?.tone === "info" || !options?.tone ? "default" : options.tone }), [toast], ); // Labels created from a picker are usable right away, before the page data refreshes. const labels = useMemo(() => { const ids = new Set(serverLabels.map((l) => l.id)); return [...serverLabels, ...created.filter((l) => !ids.has(l.id))].sort((a, b) => a.name.localeCompare(b.name)); }, [serverLabels, created]); const createLabel = useCallback( async (labelName: string) => { const color = LABEL_COLORS[labels.length % LABEL_COLORS.length]; const result = await createLabelAction(owner, name, { name: labelName, color }); if (result.error || !result.label) { notify(result.error ?? "Couldn't create the label.", { tone: "error" }); return null; } setCreated((c) => [...c, result.label!]); return result.label; }, [owner, name, labels.length, notify], ); return {children}; }