full comments system

This commit is contained in:
Oliver Bryan
2026-01-21 19:10:28 +00:00
parent 0d2195cab4
commit 8f87fc8acf
28 changed files with 1451 additions and 7 deletions

View File

@@ -1,4 +1,5 @@
export * from "@/lib/query/hooks/derived";
export * from "@/lib/query/hooks/issue-comments";
export * from "@/lib/query/hooks/issues";
export * from "@/lib/query/hooks/organisations";
export * from "@/lib/query/hooks/projects";

View File

@@ -0,0 +1,44 @@
import type {
IssueCommentCreateRequest,
IssueCommentDeleteRequest,
IssueCommentRecord,
IssueCommentResponse,
SuccessResponse,
} from "@sprint/shared";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query/keys";
import { issueComment } from "@/lib/server";
export function useIssueComments(issueId?: number | null) {
return useQuery<IssueCommentResponse[]>({
queryKey: queryKeys.issueComments.byIssue(issueId ?? 0),
queryFn: () => issueComment.byIssue(issueId ?? 0),
enabled: Boolean(issueId),
});
}
export function useCreateIssueComment() {
const queryClient = useQueryClient();
return useMutation<IssueCommentRecord, Error, IssueCommentCreateRequest>({
mutationKey: ["issue-comments", "create"],
mutationFn: issueComment.create,
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: queryKeys.issueComments.byIssue(variables.issueId),
});
},
});
}
export function useDeleteIssueComment() {
const queryClient = useQueryClient();
return useMutation<SuccessResponse, Error, IssueCommentDeleteRequest>({
mutationKey: ["issue-comments", "delete"],
mutationFn: issueComment.delete,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.issueComments.all });
},
});
}

View File

@@ -16,6 +16,10 @@ export const queryKeys = {
statusCount: (organisationId: number, status: string) =>
[...queryKeys.issues.all, "status-count", organisationId, status] as const,
},
issueComments: {
all: ["issue-comments"] as const,
byIssue: (issueId: number) => [...queryKeys.issueComments.all, "by-issue", issueId] as const,
},
sprints: {
all: ["sprints"] as const,
byProject: (projectId: number) => [...queryKeys.sprints.all, "by-project", projectId] as const,

View File

@@ -1,6 +1,7 @@
import type { ApiError } from "@sprint/shared";
export * as issue from "@/lib/server/issue";
export * as issueComment from "@/lib/server/issue-comment";
export * as organisation from "@/lib/server/organisation";
export * as project from "@/lib/server/project";
export * as sprint from "@/lib/server/sprint";

View File

@@ -0,0 +1,19 @@
import type { IssueCommentResponse } from "@sprint/shared";
import { getServerURL } from "@/lib/utils";
import { getErrorMessage } from "..";
export async function byIssue(issueId: number): Promise<IssueCommentResponse[]> {
const url = new URL(`${getServerURL()}/issue-comments/by-issue`);
url.searchParams.set("issueId", `${issueId}`);
const res = await fetch(url.toString(), {
credentials: "include",
});
if (!res.ok) {
const message = await getErrorMessage(res, `failed to get issue comments (${res.status})`);
throw new Error(message);
}
return res.json();
}

View File

@@ -0,0 +1,29 @@
import type { IssueCommentCreateRequest, IssueCommentRecord } from "@sprint/shared";
import { getCsrfToken, getServerURL } from "@/lib/utils";
import { getErrorMessage } from "..";
export async function create(request: IssueCommentCreateRequest): Promise<IssueCommentRecord> {
const csrfToken = getCsrfToken();
const res = await fetch(`${getServerURL()}/issue-comment/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
},
body: JSON.stringify(request),
credentials: "include",
});
if (!res.ok) {
const message = await getErrorMessage(res, `failed to create comment (${res.status})`);
throw new Error(message);
}
const data = (await res.json()) as IssueCommentRecord;
if (!data.id) {
throw new Error(`failed to create comment (${res.status})`);
}
return data;
}

View File

@@ -0,0 +1,24 @@
import type { IssueCommentDeleteRequest, SuccessResponse } from "@sprint/shared";
import { getCsrfToken, getServerURL } from "@/lib/utils";
import { getErrorMessage } from "..";
export async function remove(request: IssueCommentDeleteRequest): Promise<SuccessResponse> {
const csrfToken = getCsrfToken();
const res = await fetch(`${getServerURL()}/issue-comment/delete`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
},
body: JSON.stringify(request),
credentials: "include",
});
if (!res.ok) {
const message = await getErrorMessage(res, `failed to delete comment (${res.status})`);
throw new Error(message);
}
return res.json();
}

View File

@@ -0,0 +1,3 @@
export { byIssue } from "@/lib/server/issue-comment/byIssue";
export { create } from "@/lib/server/issue-comment/create";
export { remove as delete } from "@/lib/server/issue-comment/delete";