fix(dashboard): prevent polish/submit race, narrow cards payload, tighten form validation — Task 8
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f55ca0114
commit
71c808ed05
2 changed files with 16 additions and 8 deletions
|
|
@ -95,7 +95,7 @@ export default async function DashboardPage({
|
|||
projectId={board.project.id}
|
||||
clientSlug={clientSlug}
|
||||
projectSlug={projectSlug}
|
||||
cards={board.cards}
|
||||
cards={board.cards.map((c) => ({ id: c.id, number: c.number, title: c.title }))}
|
||||
assistantAvailable={await aiConfigured()}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { useRef, useState, useTransition } from "react";
|
||||
import { Plus, Sparkles } from "lucide-react";
|
||||
|
||||
import { logWorkAction, polishNoteAction } from "@/app/actions/log-work";
|
||||
import type { CardRecord } from "@/lib/pb/types";
|
||||
|
||||
/**
|
||||
* The owner's way to put off-repo work onto the dashboard.
|
||||
|
|
@ -23,10 +22,12 @@ export function LogWorkPanel({
|
|||
projectId: string;
|
||||
clientSlug: string;
|
||||
projectSlug: string;
|
||||
cards: CardRecord[];
|
||||
cards: { id: string; number: number; title: string }[];
|
||||
assistantAvailable: boolean;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const openRef = useRef(open);
|
||||
openRef.current = open;
|
||||
const [date, setDate] = useState(() => new Date().toISOString().slice(0, 10));
|
||||
const [hours, setHours] = useState("");
|
||||
const [message, setMessage] = useState("");
|
||||
|
|
@ -54,7 +55,10 @@ export function LogWorkPanel({
|
|||
function submit(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const parsedHours = Number(hours);
|
||||
if (!message.trim() || !Number.isFinite(parsedHours) || parsedHours <= 0) return;
|
||||
if (!message.trim() || !Number.isFinite(parsedHours) || parsedHours <= 0) {
|
||||
setError("A message and a positive number of hours are required");
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
|
||||
startTransition(async () => {
|
||||
|
|
@ -86,6 +90,7 @@ export function LogWorkPanel({
|
|||
setError(null);
|
||||
try {
|
||||
const result = await polishNoteAction({ text: message });
|
||||
if (!openRef.current) return; // the panel closed (e.g. via a submit) while this was in flight
|
||||
if (result.ok && result.text) setMessage(result.text);
|
||||
else setError(result.message ?? "Could not polish that note");
|
||||
} finally {
|
||||
|
|
@ -103,6 +108,7 @@ export function LogWorkPanel({
|
|||
<span className="text-xs text-text-muted">Date</span>
|
||||
<input
|
||||
type="date"
|
||||
required
|
||||
value={date}
|
||||
onChange={(event) => setDate(event.target.value)}
|
||||
className={field}
|
||||
|
|
@ -113,8 +119,9 @@ export function LogWorkPanel({
|
|||
<span className="text-xs text-text-muted">Hours</span>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
step="0.05"
|
||||
min="0.1"
|
||||
max="24"
|
||||
value={hours}
|
||||
onChange={(event) => setHours(event.target.value)}
|
||||
placeholder="3.5"
|
||||
|
|
@ -126,6 +133,7 @@ export function LogWorkPanel({
|
|||
<label className="block space-y-1">
|
||||
<span className="text-xs text-text-muted">What was worked on</span>
|
||||
<textarea
|
||||
autoFocus
|
||||
rows={3}
|
||||
value={message}
|
||||
onChange={(event) => setMessage(event.target.value)}
|
||||
|
|
@ -160,7 +168,7 @@ export function LogWorkPanel({
|
|||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending || !message.trim() || !hours}
|
||||
disabled={pending || polishing || !message.trim() || !hours}
|
||||
className="rounded-lg bg-brand px-3 py-2 text-sm font-medium text-brand-fg disabled:opacity-50"
|
||||
>
|
||||
{pending ? "Logging…" : "Log work"}
|
||||
|
|
@ -170,7 +178,7 @@ export function LogWorkPanel({
|
|||
<button
|
||||
type="button"
|
||||
onClick={polish}
|
||||
disabled={polishing || !message.trim()}
|
||||
disabled={polishing || pending || !message.trim()}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-2.5 py-2 text-xs text-text-secondary hover:bg-surface-raised disabled:opacity-50"
|
||||
>
|
||||
<Sparkles size={13} strokeWidth={2} />
|
||||
|
|
|
|||
Loading…
Reference in a new issue