"use client"; import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { companyGoalsApi, type CompanyGoalsUpdate, } from "@/lib/api/company-goals"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { Target, Save } from "lucide-react"; import { toast } from "sonner"; function parseError(e: unknown): string { return e instanceof Error ? e.message : "parse error"; } export function CompanyGoalsCard() { const queryClient = useQueryClient(); // null = "show the server value"; deriving the displayed value avoids syncing // query state into local state with an effect (react-hooks/set-state-in-effect). const [northStar, setNorthStar] = useState(null); const [constraints, setConstraints] = useState(null); const [objectives, setObjectives] = useState(null); const [policy, setPolicy] = useState(null); const [brandVoice, setBrandVoice] = useState(null); const { data: goals, isLoading } = useQuery({ queryKey: ["company-goals"], queryFn: companyGoalsApi.get, }); const northStarVal = northStar ?? goals?.north_star ?? ""; const constraintsVal = constraints ?? (goals?.constraints ?? []).join("\n"); const objectivesVal = objectives ?? JSON.stringify(goals?.objectives ?? [], null, 2); const policyVal = policy ?? JSON.stringify(goals?.operating_policy ?? {}, null, 2); const brandVoiceVal = brandVoice ?? goals?.brand_voice ?? ""; const saveMutation = useMutation({ mutationFn: (update: CompanyGoalsUpdate) => companyGoalsApi.update(update), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["company-goals"] }); setNorthStar(null); setConstraints(null); setObjectives(null); setPolicy(null); setBrandVoice(null); toast.success("Company charter updated"); }, onError: (error) => { toast.error(`Failed to save: ${parseError(error)}`); }, }); const handleSave = () => { let parsedObjectives: Record[]; let parsedPolicy: Record; try { parsedObjectives = JSON.parse(objectivesVal); if (!Array.isArray(parsedObjectives)) { throw new Error("must be a JSON array"); } } catch (e) { toast.error(`Objectives: invalid JSON — ${parseError(e)}`); return; } try { parsedPolicy = JSON.parse(policyVal); if ( typeof parsedPolicy !== "object" || parsedPolicy === null || Array.isArray(parsedPolicy) ) { throw new Error("must be a JSON object"); } } catch (e) { toast.error(`Operating policy: invalid JSON — ${parseError(e)}`); return; } saveMutation.mutate({ north_star: northStarVal, objectives: parsedObjectives, constraints: constraintsVal .split("\n") .map((c) => c.trim()) .filter(Boolean), operating_policy: parsedPolicy, brand_voice: brandVoiceVal, }); }; return ( Company Charter The CEO-owned north star, objectives, constraints, operating policy, and brand voice. Injected into every agent's briefing so all work stays goal-aware.