diff --git a/apps/Backend/src/routes/insuranceCreds.ts b/apps/Backend/src/routes/insuranceCreds.ts index 4d5c2ba..f2058da 100644 --- a/apps/Backend/src/routes/insuranceCreds.ts +++ b/apps/Backend/src/routes/insuranceCreds.ts @@ -39,12 +39,24 @@ router.post("/", async (req: Request, res: Response):Promise => { const parseResult = insertInsuranceCredentialSchema.safeParse({ ...req.body, userId }); if (!parseResult.success) { - return res.status(400).json({ error: parseResult.error.flatten() }); + const flat = (parseResult as typeof parseResult & { error: z.ZodError }).error.flatten(); + const firstError = + Object.values(flat.fieldErrors)[0]?.[0] || "Invalid input"; + + return res.status(400).json({ + message: firstError, + details: flat.fieldErrors, + }); } const credential = await storage.createInsuranceCredential(parseResult.data); return res.status(201).json(credential); - } catch (err) { + } catch (err:any) { + if (err.code === "P2002") { + return res.status(400).json({ + message: `Credential with this ${err.meta?.target?.join(", ")} already exists.`, + }); + } return res.status(500).json({ error: "Failed to create credential", details: String(err) }); } }); diff --git a/apps/Frontend/src/components/settings/InsuranceCredForm.tsx b/apps/Frontend/src/components/settings/InsuranceCredForm.tsx index f8e4d6a..cdb62f7 100644 --- a/apps/Frontend/src/components/settings/InsuranceCredForm.tsx +++ b/apps/Frontend/src/components/settings/InsuranceCredForm.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@/lib/queryClient"; import { toast } from "@/hooks/use-toast"; @@ -6,15 +6,23 @@ import { toast } from "@/hooks/use-toast"; type CredentialFormProps = { onClose: () => void; userId: number; + defaultValues?: { + id?: number; + siteKey: string; + username: string; + password: string; + }; }; -export function CredentialForm({ onClose, userId }: CredentialFormProps) { - const [siteKey, setSiteKey] = useState(""); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); +export function CredentialForm({ onClose, userId, defaultValues }: CredentialFormProps) { + const [siteKey, setSiteKey] = useState(defaultValues?.siteKey || ""); + const [username, setUsername] = useState(defaultValues?.username || ""); + const [password, setPassword] = useState(defaultValues?.password || ""); + const queryClient = useQueryClient(); - const createCredentialMutation = useMutation({ + // Create or Update Mutation inside form + const mutation = useMutation({ mutationFn: async () => { const payload = { siteKey: siteKey.trim(), @@ -23,15 +31,24 @@ export function CredentialForm({ onClose, userId }: CredentialFormProps) { userId, }; - const res = await apiRequest("POST", "/api/insuranceCreds/", payload); + const url = defaultValues?.id + ? `/api/insuranceCreds/${defaultValues.id}` + : "/api/insuranceCreds/"; + + const method = defaultValues?.id ? "PUT" : "POST"; + + const res = await apiRequest(method, url, payload); + if (!res.ok) { const errorData = await res.json().catch(() => null); - throw new Error(errorData?.message || "Failed to create credential"); + throw new Error(errorData?.message || "Failed to save credential"); } return res.json(); }, onSuccess: () => { - toast({ title: "Credential created." }); + toast({ + title: `Credential ${defaultValues?.id ? "updated" : "created"}.`, + }); queryClient.invalidateQueries({ queryKey: ["/api/insuranceCreds/"] }); onClose(); }, @@ -44,8 +61,16 @@ export function CredentialForm({ onClose, userId }: CredentialFormProps) { }, }); + // Reset form on defaultValues change (edit mode) + useEffect(() => { + setSiteKey(defaultValues?.siteKey || ""); + setUsername(defaultValues?.username || ""); + setPassword(defaultValues?.password || ""); + }, [defaultValues]); + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); + if (!siteKey || !username || !password) { toast({ title: "Error", @@ -54,13 +79,16 @@ export function CredentialForm({ onClose, userId }: CredentialFormProps) { }); return; } - createCredentialMutation.mutate(); + + mutation.mutate(); }; return (
-

Create Credential

+

+ {defaultValues?.id ? "Edit Credential" : "Create Credential"} +

@@ -69,7 +97,7 @@ export function CredentialForm({ onClose, userId }: CredentialFormProps) { value={siteKey} onChange={(e) => setSiteKey(e.target.value)} className="mt-1 p-2 border rounded w-full" - placeholder="e.g., github, slack" + placeholder="e.g., MH, Delta MA, (keep the site key exact same)" />
@@ -95,15 +123,22 @@ export function CredentialForm({ onClose, userId }: CredentialFormProps) { type="button" onClick={onClose} className="text-gray-600 hover:underline" + disabled={mutation.isPending} > Cancel
diff --git a/apps/Frontend/src/components/settings/insuranceCredTable.tsx b/apps/Frontend/src/components/settings/insuranceCredTable.tsx index 827eea8..f8d6e70 100644 --- a/apps/Frontend/src/components/settings/insuranceCredTable.tsx +++ b/apps/Frontend/src/components/settings/insuranceCredTable.tsx @@ -1,7 +1,10 @@ -// components/CredentialTable.tsx -import { useQuery } from "@tanstack/react-query"; +import React, { useState } from "react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@/lib/queryClient"; -import { toast } from "@/hooks/use-toast"; +import { Button } from "../ui/button"; +import { Edit, Delete, Plus } from "lucide-react"; +import { CredentialForm } from "./InsuranceCredForm"; +import { DeleteConfirmationDialog } from "../ui/deleteDialog"; type Credential = { id: number; @@ -11,47 +14,221 @@ type Credential = { }; export function CredentialTable() { - const { data, isLoading, error } = useQuery({ - queryKey: ["/api/credentials/"], + const queryClient = useQueryClient(); + + // Fetch current user + const { + data: currentUser, + isLoading: isUserLoading, + isError: isUserError, + } = useQuery({ + queryKey: ["/api/users/"], + queryFn: async () => { + const res = await apiRequest("GET", "/api/users/"); + if (!res.ok) throw new Error("Failed to fetch user"); + return res.json(); + }, + }); + + const [currentPage, setCurrentPage] = useState(1); + const [modalOpen, setModalOpen] = useState(false); + const [editingCred, setEditingCred] = useState(null); + + const credentialsPerPage = 5; + + const { data: credentials = [], isLoading, error } = useQuery({ + queryKey: ["/api/insuranceCreds/"], queryFn: async () => { const res = await apiRequest("GET", "/api/insuranceCreds/"); - if (!res.ok) { - throw new Error("Failed to fetch credentials"); - } + if (!res.ok) throw new Error("Failed to fetch credentials"); return res.json() as Promise; }, }); - if (isLoading) return

Loading credentials...

; - if (error) return

Failed to load credentials.

; - if (!data || data.length === 0) - return

No credentials found.

; + const deleteMutation = useMutation({ + mutationFn: async (cred: Credential) => { + const res = await apiRequest("DELETE", `/api/insuranceCreds/${cred.id}`); + if (!res.ok) throw new Error("Failed to delete credential"); + return true; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/insuranceCreds/"] }); + }, + }); + + // New state for delete dialog + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [credentialToDelete, setCredentialToDelete] = useState(null); + + const handleDeleteClick = (cred: Credential) => { + setCredentialToDelete(cred); + setIsDeleteDialogOpen(true); + }; + + const handleConfirmDelete = () => { + if (credentialToDelete) { + deleteMutation.mutate(credentialToDelete, { + onSuccess: () => { + setIsDeleteDialogOpen(false); + setCredentialToDelete(null); + }, + }); + } + }; + + const handleCancelDelete = () => { + setIsDeleteDialogOpen(false); + setCredentialToDelete(null); + }; + + const indexOfLast = currentPage * credentialsPerPage; + const indexOfFirst = indexOfLast - credentialsPerPage; + const currentCredentials = credentials.slice(indexOfFirst, indexOfLast); + const totalPages = Math.ceil(credentials.length / credentialsPerPage); + + if (isUserLoading) return

Loading user...

; + if (isUserError) return

Error loading user

; return ( -
- - - - - - - - - - - {data.map((cred) => ( - - - - - +
+
+

Insurance Credentials

+ +
+ +
+
SiteUsernamePasswordActions
{cred.siteKey}{cred.username}{cred.password} - - -
+ + + + + + - ))} - -
+ Site Key + + Username + + Password +
+ + + {isLoading ? ( + + + Loading credentials... + + + ) : error ? ( + + + Error loading credentials + + + ) : currentCredentials.length === 0 ? ( + + + No credentials found. + + + ) : ( + currentCredentials.map((cred) => ( + + {cred.siteKey} + {cred.username} + •••••••• + + + + + + )) + )} + + +
+ + {/* Pagination */} + {credentials.length > credentialsPerPage && ( +
+
+

+ Showing {indexOfFirst + 1} to{" "} + {Math.min(indexOfLast, credentials.length)} of{" "} + {credentials.length} results +

+ + +
+
+ )} + + {/* Modal for Add/Edit */} + {modalOpen && currentUser && ( + setModalOpen(false)} + /> + )} + +
); } diff --git a/apps/Frontend/src/pages/settings-page.tsx b/apps/Frontend/src/pages/settings-page.tsx index f8f94f4..67fec96 100644 --- a/apps/Frontend/src/pages/settings-page.tsx +++ b/apps/Frontend/src/pages/settings-page.tsx @@ -281,7 +281,6 @@ export default function SettingsPage() { }); - return (
)} + {/* User Setting section */}

User Settings

@@ -399,31 +399,10 @@ export default function SettingsPage() {
- {/* Credential Section */} -
- - - - -

Saved Credentials

- -
-
+ {/* Credential Section */} +
+
- - {credentialModalOpen && currentUser?.id && ( - setCredentialModalOpen(false)} - /> -)} - -