because I got bored of customising my CV for every job
at main 116 lines 3.5 kB view raw
1import { Button, TextInput, useToast } from "@cv/ui"; 2import { useQueryClient } from "@tanstack/react-query"; 3import { useState } from "react"; 4import { useUpdateAiProviderMutation } from "@/generated/graphql"; 5import { extractGraphQLErrorMessage } from "@/utils/graphql-error"; 6 7interface Provider { 8 id: string; 9 label: string; 10 providerType: string; 11 model?: string | null | undefined; 12 baseUrl?: string | null | undefined; 13} 14 15interface Props { 16 providerId: string; 17 providers: Provider[]; 18 onClose: () => void; 19} 20 21export const EditProviderDialog = ({ 22 providerId, 23 providers, 24 onClose, 25}: Props) => { 26 const provider = providers.find((p) => p.id === providerId); 27 const { showSuccess, showError } = useToast(); 28 const queryClient = useQueryClient(); 29 const mutation = useUpdateAiProviderMutation(); 30 31 const [form, setForm] = useState({ 32 label: provider?.label ?? "", 33 model: provider?.model ?? "", 34 baseUrl: provider?.baseUrl ?? "", 35 }); 36 37 if (!provider) return null; 38 39 const handleSubmit = async (e: React.FormEvent) => { 40 e.preventDefault(); 41 42 try { 43 await mutation.mutateAsync({ 44 providerId, 45 ...(form.label.trim() !== provider.label && { 46 label: form.label.trim(), 47 }), 48 ...(form.model.trim() !== (provider.model ?? "") && { 49 model: form.model.trim() || null, 50 }), 51 ...(form.baseUrl.trim() !== (provider.baseUrl ?? "") && { 52 baseUrl: form.baseUrl.trim() || null, 53 }), 54 }); 55 await queryClient.invalidateQueries({ queryKey: ["MyAiProviders"] }); 56 showSuccess("Provider updated"); 57 onClose(); 58 } catch (err) { 59 showError(extractGraphQLErrorMessage(err) || "Failed to update provider"); 60 } 61 }; 62 63 return ( 64 <div className="fixed inset-0 z-50 flex items-center justify-center bg-ctp-crust/70"> 65 <div className="w-full max-w-md rounded-lg bg-ctp-base p-6 shadow-lg border border-ctp-surface1"> 66 <h3 className="text-lg font-medium text-ctp-text mb-4"> 67 Edit Provider 68 </h3> 69 <form onSubmit={handleSubmit} className="space-y-4"> 70 <TextInput 71 label="Label" 72 value={form.label} 73 onChange={(value: string) => 74 setForm((f) => ({ ...f, label: value })) 75 } 76 /> 77 78 <TextInput 79 label="Model (optional)" 80 value={form.model} 81 onChange={(value: string) => 82 setForm((f) => ({ ...f, model: value })) 83 } 84 placeholder={ 85 provider.providerType === "anthropic" 86 ? "claude-sonnet-4-5-20250929" 87 : "gpt-4o-mini" 88 } 89 /> 90 91 <TextInput 92 label="Base URL (optional)" 93 value={form.baseUrl} 94 onChange={(value: string) => 95 setForm((f) => ({ ...f, baseUrl: value })) 96 } 97 placeholder={ 98 provider.providerType === "anthropic" 99 ? "https://api.anthropic.com" 100 : "https://api.openai.com" 101 } 102 /> 103 104 <div className="flex gap-3"> 105 <Button type="submit" disabled={mutation.isPending}> 106 {mutation.isPending ? "Saving..." : "Save Changes"} 107 </Button> 108 <Button type="button" variant="ghost" onClick={onClose}> 109 Cancel 110 </Button> 111 </div> 112 </form> 113 </div> 114 </div> 115 ); 116};