Simplify edit state and surface edit errors to user

- Combine editingId + editingName into single editing: { id, name } | null state object
- Extract cancelEdit() helper called in 3 places instead of repeating setEditing(null)
- Tag all update-name action responses with intent: "update-name" so errors can be
  identified and routed correctly
- Show update-name errors inline below the edit input
- Guard single-add and bulk-add error displays against showing update-name errors

https://claude.ai/code/session_01Q94DWQ1HZEB9PJhGMArU8D
This commit is contained in:
Claude 2026-04-02 00:51:55 +00:00
parent e43fd17de4
commit 93fe319830
No known key found for this signature in database

View file

@ -69,17 +69,17 @@ export async function action({ request, params }: Route.ActionArgs) {
const newName = formData.get("newName");
if (typeof participantId !== "string") {
return { error: "Invalid participant" };
return { error: "Invalid participant", intent: "update-name" };
}
if (typeof newName !== "string" || !newName.trim()) {
return { error: "Name is required" };
return { error: "Name is required", intent: "update-name" };
}
const trimmedName = newName.trim();
const existing = await findParticipantByName(params.id, trimmedName);
if (existing && existing.id !== participantId) {
return { error: `"${trimmedName}" already exists in this season.` };
return { error: `"${trimmedName}" already exists in this season.`, intent: "update-name" };
}
try {
@ -87,7 +87,7 @@ export async function action({ request, params }: Route.ActionArgs) {
return { success: true, intent: "update-name" };
} catch (error) {
logger.error("Error updating participant:", error);
return { error: "Failed to update participant. Please try again." };
return { error: "Failed to update participant. Please try again.", intent: "update-name" };
}
}
@ -171,16 +171,16 @@ export async function action({ request, params }: Route.ActionArgs) {
export default function ManageParticipants({ loaderData, actionData }: Route.ComponentProps) {
const { sportsSeason, participants } = loaderData;
const [editingId, setEditingId] = useState<string | null>(null);
const [editingName, setEditingName] = useState("");
const [editing, setEditing] = useState<{ id: string; name: string } | null>(null);
const cancelEdit = () => setEditing(null);
// Use success state to reset forms by changing the key
const formKey = actionData?.success ? Date.now() : 'static';
useEffect(() => {
if (actionData?.success && actionData?.intent === "update-name") {
setEditingId(null);
setEditingName("");
cancelEdit();
}
}, [actionData]);
@ -221,7 +221,7 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com
/>
</div>
{actionData?.error && !actionData?.count && (
{actionData?.error && !actionData?.count && actionData?.intent !== "update-name" && (
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
{actionData.error}
</div>
@ -267,7 +267,7 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com
</p>
</div>
{actionData?.error && actionData?.count === undefined && (
{actionData?.error && actionData?.count === undefined && actionData?.intent !== "update-name" && (
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
{actionData.error}
</div>
@ -318,30 +318,30 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com
{participants.map((participant) => (
<TableRow key={participant.id}>
<TableCell className="font-medium">
{editingId === participant.id ? (
{editing?.id === participant.id ? (
<div className="space-y-1">
<Input
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
value={editing.name}
onChange={(e) => setEditing({ id: participant.id, name: e.target.value })}
className="h-8"
autoFocus
onKeyDown={(e) => {
if (e.key === "Escape") {
setEditingId(null);
setEditingName("");
}
}}
onKeyDown={(e) => { if (e.key === "Escape") cancelEdit(); }}
/>
{actionData?.error && actionData?.intent === "update-name" && (
<p className="text-xs text-destructive">{actionData.error}</p>
)}
</div>
) : (
participant.name
)}
</TableCell>
<TableCell>
{editingId === participant.id ? (
{editing?.id === participant.id ? (
<div className="flex gap-1">
<Form method="post">
<input type="hidden" name="intent" value="update-name" />
<input type="hidden" name="participantId" value={participant.id} />
<input type="hidden" name="newName" value={editingName} />
<input type="hidden" name="newName" value={editing.name} />
<Button
type="submit"
variant="ghost"
@ -356,10 +356,7 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={() => {
setEditingId(null);
setEditingName("");
}}
onClick={cancelEdit}
>
<X className="h-4 w-4" />
</Button>
@ -371,10 +368,7 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={() => {
setEditingId(participant.id);
setEditingName(participant.name);
}}
onClick={() => setEditing({ id: participant.id, name: participant.name })}
>
<Pencil className="h-4 w-4" />
</Button>