fix(comments): replace optimistic updates with loading state
- Remove temp-xxx optimistic inserts from submitComment/submitReply - Wait for API response, then insert real comment into timeline - Add Loader2 spinner to comment/reply submit buttons during loading - Remove hover card from Markdown.tsx (will be handled via NodeView later) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b9ea10c89d
commit
98829fad29
4 changed files with 15 additions and 47 deletions
|
|
@ -3,7 +3,6 @@ import ReactMarkdown, { type Components } from 'react-markdown'
|
|||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { MentionHoverCard } from '@/components/common/mention-hover-card'
|
||||
import { CodeBlock, InlineCode } from './CodeBlock'
|
||||
import { preprocessLinks } from './linkify'
|
||||
|
||||
|
|
@ -61,15 +60,10 @@ function createComponents(
|
|||
a: ({ href, children }) => {
|
||||
// Mention links: mention://member/id or mention://agent/id
|
||||
if (href?.startsWith('mention://')) {
|
||||
const parts = href.replace('mention://', '').split('/')
|
||||
const mentionType = parts[0] ?? 'member'
|
||||
const mentionId = parts[1] ?? ''
|
||||
return (
|
||||
<MentionHoverCard type={mentionType} id={mentionId}>
|
||||
<span className="text-primary font-semibold mx-0.5">
|
||||
{children}
|
||||
</span>
|
||||
</MentionHoverCard>
|
||||
<span className="text-primary font-semibold mx-0.5">
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { ArrowUp } from "lucide-react";
|
||||
import { ArrowUp, Loader2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RichTextEditor, type RichTextEditorRef } from "@/components/common/rich-text-editor";
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ function CommentInput({ onSubmit }: CommentInputProps) {
|
|||
disabled={isEmpty || submitting}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<ArrowUp className="h-4 w-4" />
|
||||
{submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <ArrowUp className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { ArrowUp } from "lucide-react";
|
||||
import { ArrowUp, Loader2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RichTextEditor, type RichTextEditorRef } from "@/components/common/rich-text-editor";
|
||||
import { ActorAvatar } from "@/components/common/actor-avatar";
|
||||
|
|
@ -83,7 +83,7 @@ function ReplyInput({
|
|||
onClick={handleSubmit}
|
||||
tabIndex={isEmpty ? -1 : 0}
|
||||
>
|
||||
<ArrowUp className="h-3.5 w-3.5" />
|
||||
{submitting ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <ArrowUp className="h-3.5 w-3.5" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -176,27 +176,14 @@ export function useIssueTimeline(issueId: string, userId?: string) {
|
|||
const submitComment = useCallback(
|
||||
async (content: string) => {
|
||||
if (!content.trim() || submitting || !userId) return;
|
||||
const tempId = "temp-" + Date.now();
|
||||
const tempEntry: TimelineEntry = {
|
||||
type: "comment",
|
||||
id: tempId,
|
||||
actor_type: "member",
|
||||
actor_id: userId,
|
||||
content,
|
||||
parent_id: null,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
comment_type: "comment",
|
||||
};
|
||||
setTimeline((prev) => [...prev, tempEntry]);
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const comment = await api.createComment(issueId, content);
|
||||
setTimeline((prev) =>
|
||||
prev.map((e) => (e.id === tempId ? commentToTimelineEntry(comment) : e)),
|
||||
);
|
||||
setTimeline((prev) => {
|
||||
if (prev.some((e) => e.id === comment.id)) return prev;
|
||||
return [...prev, commentToTimelineEntry(comment)];
|
||||
});
|
||||
} catch {
|
||||
setTimeline((prev) => prev.filter((e) => e.id !== tempId));
|
||||
toast.error("Failed to send comment");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
|
|
@ -208,26 +195,13 @@ export function useIssueTimeline(issueId: string, userId?: string) {
|
|||
const submitReply = useCallback(
|
||||
async (parentId: string, content: string) => {
|
||||
if (!content.trim() || !userId) return;
|
||||
const tempId = "temp-" + Date.now();
|
||||
const tempEntry: TimelineEntry = {
|
||||
type: "comment",
|
||||
id: tempId,
|
||||
actor_type: "member",
|
||||
actor_id: userId,
|
||||
content,
|
||||
parent_id: parentId,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
comment_type: "comment",
|
||||
};
|
||||
setTimeline((prev) => [...prev, tempEntry]);
|
||||
try {
|
||||
const comment = await api.createComment(issueId, content, "comment", parentId);
|
||||
setTimeline((prev) =>
|
||||
prev.map((e) => (e.id === tempId ? commentToTimelineEntry(comment) : e)),
|
||||
);
|
||||
setTimeline((prev) => {
|
||||
if (prev.some((e) => e.id === comment.id)) return prev;
|
||||
return [...prev, commentToTimelineEntry(comment)];
|
||||
});
|
||||
} catch {
|
||||
setTimeline((prev) => prev.filter((e) => e.id !== tempId));
|
||||
toast.error("Failed to send reply");
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue