From 45343571f579afa840861e507c890d829dcf3569 Mon Sep 17 00:00:00 2001 From: Oliver Bryan <04oliverbryan@gmail.com> Date: Tue, 20 Jan 2026 16:59:32 +0000 Subject: [PATCH] refactored frontend api helpers to promise interface --- packages/frontend/src/lib/server/index.ts | 23 +++++++-- .../src/lib/server/issue/byProject.ts | 21 +++----- .../frontend/src/lib/server/issue/create.ts | 29 +++++------ .../frontend/src/lib/server/issue/delete.ts | 23 +++------ .../src/lib/server/issue/replaceStatus.ts | 22 +++------ .../src/lib/server/issue/statusCount.ts | 22 +++------ .../frontend/src/lib/server/issue/update.ts | 44 +++-------------- .../src/lib/server/organisation/addMember.ts | 20 +++----- .../src/lib/server/organisation/byUser.ts | 15 +++--- .../src/lib/server/organisation/create.ts | 31 +++++------- .../src/lib/server/organisation/delete.ts | 25 +++------- .../src/lib/server/organisation/members.ts | 19 +++---- .../lib/server/organisation/removeMember.ts | 20 +++----- .../src/lib/server/organisation/update.ts | 43 +++------------- .../server/organisation/updateMemberRole.ts | 22 +++------ .../src/lib/server/project/byOrganisation.ts | 21 +++----- .../frontend/src/lib/server/project/create.ts | 29 +++++------ .../frontend/src/lib/server/project/delete.ts | 23 +++------ .../frontend/src/lib/server/project/update.ts | 35 +++---------- .../src/lib/server/sprint/byProject.ts | 19 +++---- .../frontend/src/lib/server/sprint/create.ts | 49 +++++-------------- .../frontend/src/lib/server/sprint/delete.ts | 23 +++------ .../frontend/src/lib/server/sprint/update.ts | 40 ++++----------- packages/frontend/src/lib/server/timer/end.ts | 20 +++----- packages/frontend/src/lib/server/timer/get.ts | 21 +++----- .../src/lib/server/timer/getInactive.ts | 22 +++------ .../frontend/src/lib/server/timer/list.ts | 26 +++++----- .../frontend/src/lib/server/timer/toggle.ts | 20 +++----- .../src/lib/server/user/byUsername.ts | 21 +++----- .../frontend/src/lib/server/user/update.ts | 29 +++++------ .../src/lib/server/user/uploadAvatar.ts | 29 ++++------- 31 files changed, 253 insertions(+), 553 deletions(-) diff --git a/packages/frontend/src/lib/server/index.ts b/packages/frontend/src/lib/server/index.ts index 82a7790..b5e55f2 100644 --- a/packages/frontend/src/lib/server/index.ts +++ b/packages/frontend/src/lib/server/index.ts @@ -7,13 +7,26 @@ export * as sprint from "@/lib/server/sprint"; export * as timer from "@/lib/server/timer"; export * as user from "@/lib/server/user"; -export type ServerQueryInput = { - onSuccess?: (data: T, res: Response) => void; - onError?: (error: ApiError | string) => void; -}; +export async function getErrorMessage(res: Response, fallback: string): Promise { + const error = await res.json().catch(() => res.text()); + if (typeof error === "string") { + return error || fallback; + } + if (error && typeof error === "object") { + if ("details" in error && error.details) { + const messages = Object.values(error.details as Record).flat(); + if (messages.length > 0) return messages.join(", "); + } + if ("error" in error && typeof error.error === "string") { + return error.error || fallback; + } + } + return fallback; +} -export function parseError(error: ApiError | string): string { +export function parseError(error: ApiError | string | Error): string { if (typeof error === "string") return error; + if (error instanceof Error) return error.message; if (error.details) { const messages = Object.values(error.details).flat(); return messages.join(", "); diff --git a/packages/frontend/src/lib/server/issue/byProject.ts b/packages/frontend/src/lib/server/issue/byProject.ts index d3d4067..3f21113 100644 --- a/packages/frontend/src/lib/server/issue/byProject.ts +++ b/packages/frontend/src/lib/server/issue/byProject.ts @@ -1,13 +1,8 @@ +import type { IssueResponse } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function byProject({ - projectId, - onSuccess, - onError, -}: { - projectId: number; -} & ServerQueryInput) { +export async function byProject(projectId: number): Promise { const url = new URL(`${getServerURL()}/issues/by-project`); url.searchParams.set("projectId", `${projectId}`); @@ -16,11 +11,9 @@ export async function byProject({ }); if (!res.ok) { - const error = await res.text(); - onError?.(error || `failed to get issues by project (${res.status})`); - } else { - const data = await res.json(); - - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get issues by project (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/issue/create.ts b/packages/frontend/src/lib/server/issue/create.ts index 8a87f1d..bcf279a 100644 --- a/packages/frontend/src/lib/server/issue/create.ts +++ b/packages/frontend/src/lib/server/issue/create.ts @@ -1,10 +1,8 @@ import type { IssueCreateRequest, IssueRecord } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function create(request: IssueCreateRequest & ServerQueryInput) { - const { onSuccess, onError, ...body } = request; +export async function create(request: IssueCreateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/issue/create`, { @@ -13,23 +11,18 @@ export async function create(request: IssueCreateRequest & ServerQueryInput res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to create issue (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - if (!data.id) { - toast.error(`failed to create issue (${res.status})`); - onError?.(`failed to create issue (${res.status})`); - return; - } - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to create issue (${res.status})`); + throw new Error(message); } + + const data = (await res.json()) as IssueRecord; + if (!data.id) { + throw new Error(`failed to create issue (${res.status})`); + } + return data; } diff --git a/packages/frontend/src/lib/server/issue/delete.ts b/packages/frontend/src/lib/server/issue/delete.ts index a2db60e..f1b56f6 100644 --- a/packages/frontend/src/lib/server/issue/delete.ts +++ b/packages/frontend/src/lib/server/issue/delete.ts @@ -1,15 +1,8 @@ import type { SuccessResponse } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function remove({ - issueId, - onSuccess, - onError, -}: { - issueId: number; -} & ServerQueryInput) { +export async function remove(issueId: number): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/issue/delete`, { @@ -23,13 +16,9 @@ export async function remove({ }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to delete issue (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to delete issue (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/issue/replaceStatus.ts b/packages/frontend/src/lib/server/issue/replaceStatus.ts index cd75cee..96c8530 100644 --- a/packages/frontend/src/lib/server/issue/replaceStatus.ts +++ b/packages/frontend/src/lib/server/issue/replaceStatus.ts @@ -1,12 +1,8 @@ import type { IssuesReplaceStatusRequest, ReplaceStatusResponse } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function replaceStatus( - request: IssuesReplaceStatusRequest & ServerQueryInput, -) { - const { onSuccess, onError, ...body } = request; +export async function replaceStatus(request: IssuesReplaceStatusRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/issues/replace-status`, { @@ -15,18 +11,14 @@ export async function replaceStatus( "Content-Type": "application/json", ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, - body: JSON.stringify(body), + body: JSON.stringify(request), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to replace status (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to replace status (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/issue/statusCount.ts b/packages/frontend/src/lib/server/issue/statusCount.ts index 82ea7c7..f1280ab 100644 --- a/packages/frontend/src/lib/server/issue/statusCount.ts +++ b/packages/frontend/src/lib/server/issue/statusCount.ts @@ -1,15 +1,8 @@ +import type { StatusCountResponse } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function statusCount({ - organisationId, - status, - onSuccess, - onError, -}: { - organisationId: number; - status: string; -} & ServerQueryInput) { +export async function statusCount(organisationId: number, status: string): Promise { const url = new URL(`${getServerURL()}/issues/status-count`); url.searchParams.set("organisationId", `${organisationId}`); url.searchParams.set("status", status); @@ -19,10 +12,9 @@ export async function statusCount({ }); if (!res.ok) { - const error = await res.text(); - onError?.(error || `failed to get issue status count (${res.status})`); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get issue status count (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/issue/update.ts b/packages/frontend/src/lib/server/issue/update.ts index 0ee109e..2760de8 100644 --- a/packages/frontend/src/lib/server/issue/update.ts +++ b/packages/frontend/src/lib/server/issue/update.ts @@ -1,25 +1,8 @@ -import type { IssueRecord } from "@sprint/shared"; -import { toast } from "sonner"; +import type { IssueRecord, IssueUpdateRequest } from "@sprint/shared"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function update({ - issueId, - title, - description, - sprintId, - assigneeIds, - status, - onSuccess, - onError, -}: { - issueId: number; - title?: string; - description?: string; - sprintId?: number | null; - assigneeIds?: number[] | null; - status?: string; -} & ServerQueryInput) { +export async function update(input: IssueUpdateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/issue/update`, { @@ -28,25 +11,14 @@ export async function update({ "Content-Type": "application/json", ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, - body: JSON.stringify({ - id: issueId, - title, - description, - sprintId, - assigneeIds, - status, - }), + body: JSON.stringify(input), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to update issue (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to update issue (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/organisation/addMember.ts b/packages/frontend/src/lib/server/organisation/addMember.ts index 025542b..173d075 100644 --- a/packages/frontend/src/lib/server/organisation/addMember.ts +++ b/packages/frontend/src/lib/server/organisation/addMember.ts @@ -1,10 +1,8 @@ import type { OrgAddMemberRequest, OrganisationMemberRecord } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function addMember(request: OrgAddMemberRequest & ServerQueryInput) { - const { onSuccess, onError, ...body } = request; +export async function addMember(request: OrgAddMemberRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/organisation/add-member`, { @@ -13,18 +11,14 @@ export async function addMember(request: OrgAddMemberRequest & ServerQueryInput< "Content-Type": "application/json", ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, - body: JSON.stringify(body), + body: JSON.stringify(request), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to add member (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to add member (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/organisation/byUser.ts b/packages/frontend/src/lib/server/organisation/byUser.ts index 3dcbd98..3f17d72 100644 --- a/packages/frontend/src/lib/server/organisation/byUser.ts +++ b/packages/frontend/src/lib/server/organisation/byUser.ts @@ -1,19 +1,16 @@ import type { OrganisationResponse } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function byUser({ onSuccess, onError }: ServerQueryInput) { +export async function byUser(): Promise { const res = await fetch(`${getServerURL()}/organisations/by-user`, { credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to get organisations (${res.status})`; - onError?.(message); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get organisations (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/organisation/create.ts b/packages/frontend/src/lib/server/organisation/create.ts index c9c63ba..929bd67 100644 --- a/packages/frontend/src/lib/server/organisation/create.ts +++ b/packages/frontend/src/lib/server/organisation/create.ts @@ -1,10 +1,8 @@ import type { OrganisationRecord, OrgCreateRequest } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function create(request: OrgCreateRequest & ServerQueryInput) { - const { onSuccess, onError, ...body } = request; +export async function create(request: OrgCreateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/organisation/create`, { @@ -13,25 +11,18 @@ export async function create(request: OrgCreateRequest & ServerQueryInput res.text()); - const message = - typeof error === "string" - ? error - : error.error || `failed to create organisation (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - if (!data.id) { - toast.error(`failed to create organisation (${res.status})`); - onError?.(`failed to create organisation (${res.status})`); - return; - } - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to create organisation (${res.status})`); + throw new Error(message); } + + const data = (await res.json()) as OrganisationRecord; + if (!data.id) { + throw new Error(`failed to create organisation (${res.status})`); + } + return data; } diff --git a/packages/frontend/src/lib/server/organisation/delete.ts b/packages/frontend/src/lib/server/organisation/delete.ts index 26b8ebd..9fa183d 100644 --- a/packages/frontend/src/lib/server/organisation/delete.ts +++ b/packages/frontend/src/lib/server/organisation/delete.ts @@ -1,15 +1,8 @@ import type { SuccessResponse } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function remove({ - organisationId, - onSuccess, - onError, -}: { - organisationId: number; -} & ServerQueryInput) { +export async function remove(organisationId: number): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/organisation/delete`, { @@ -23,15 +16,9 @@ export async function remove({ }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" - ? error - : error.error || `failed to delete organisation (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to delete organisation (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/organisation/members.ts b/packages/frontend/src/lib/server/organisation/members.ts index 3d296a8..9622c92 100644 --- a/packages/frontend/src/lib/server/organisation/members.ts +++ b/packages/frontend/src/lib/server/organisation/members.ts @@ -1,14 +1,8 @@ import type { OrganisationMemberResponse } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function members({ - organisationId, - onSuccess, - onError, -}: { - organisationId: number; -} & ServerQueryInput) { +export async function members(organisationId: number): Promise { const url = new URL(`${getServerURL()}/organisation/members`); url.searchParams.set("organisationId", `${organisationId}`); @@ -17,10 +11,9 @@ export async function members({ }); if (!res.ok) { - const error = await res.text(); - onError?.(error || `failed to get members (${res.status})`); - } else { - const data = (await res.json()) as OrganisationMemberResponse[]; - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get members (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/organisation/removeMember.ts b/packages/frontend/src/lib/server/organisation/removeMember.ts index 7969742..dc31b4e 100644 --- a/packages/frontend/src/lib/server/organisation/removeMember.ts +++ b/packages/frontend/src/lib/server/organisation/removeMember.ts @@ -1,10 +1,8 @@ import type { OrgRemoveMemberRequest, SuccessResponse } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function removeMember(request: OrgRemoveMemberRequest & ServerQueryInput) { - const { onSuccess, onError, ...body } = request; +export async function removeMember(request: OrgRemoveMemberRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/organisation/remove-member`, { @@ -13,18 +11,14 @@ export async function removeMember(request: OrgRemoveMemberRequest & ServerQuery "Content-Type": "application/json", ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, - body: JSON.stringify(body), + body: JSON.stringify(request), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to remove member (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to remove member (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/organisation/update.ts b/packages/frontend/src/lib/server/organisation/update.ts index eaf872d..0a980f1 100644 --- a/packages/frontend/src/lib/server/organisation/update.ts +++ b/packages/frontend/src/lib/server/organisation/update.ts @@ -1,23 +1,8 @@ -import type { OrganisationRecord } from "@sprint/shared"; -import { toast } from "sonner"; +import type { OrganisationRecord, OrgUpdateRequest } from "@sprint/shared"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function update({ - organisationId, - name, - description, - slug, - statuses, - onSuccess, - onError, -}: { - organisationId: number; - name?: string; - description?: string; - slug?: string; - statuses?: Record; -} & ServerQueryInput) { +export async function update(input: OrgUpdateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/organisation/update`, { @@ -26,26 +11,14 @@ export async function update({ "Content-Type": "application/json", ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, - body: JSON.stringify({ - id: organisationId, - name, - description, - slug, - statuses, - }), + body: JSON.stringify(input), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" - ? error - : error.error || `failed to update organisation (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to update organisation (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/organisation/updateMemberRole.ts b/packages/frontend/src/lib/server/organisation/updateMemberRole.ts index eb295b6..a0eb9f9 100644 --- a/packages/frontend/src/lib/server/organisation/updateMemberRole.ts +++ b/packages/frontend/src/lib/server/organisation/updateMemberRole.ts @@ -1,12 +1,10 @@ import type { OrganisationMemberRecord, OrgUpdateMemberRoleRequest } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; export async function updateMemberRole( - request: OrgUpdateMemberRoleRequest & ServerQueryInput, -) { - const { onSuccess, onError, ...body } = request; + request: OrgUpdateMemberRoleRequest, +): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/organisation/update-member-role`, { @@ -15,18 +13,14 @@ export async function updateMemberRole( "Content-Type": "application/json", ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, - body: JSON.stringify(body), + body: JSON.stringify(request), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to update member role (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to update member role (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/project/byOrganisation.ts b/packages/frontend/src/lib/server/project/byOrganisation.ts index c3c0251..8ca2130 100644 --- a/packages/frontend/src/lib/server/project/byOrganisation.ts +++ b/packages/frontend/src/lib/server/project/byOrganisation.ts @@ -1,13 +1,8 @@ +import type { ProjectResponse } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function byOrganisation({ - organisationId, - onSuccess, - onError, -}: { - organisationId: number; -} & ServerQueryInput) { +export async function byOrganisation(organisationId: number): Promise { const url = new URL(`${getServerURL()}/projects/by-organisation`); url.searchParams.set("organisationId", `${organisationId}`); @@ -16,11 +11,9 @@ export async function byOrganisation({ }); if (!res.ok) { - const error = await res.text(); - onError?.(error || `failed to get projects by organisation (${res.status})`); - } else { - const data = await res.json(); - - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get projects by organisation (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/project/create.ts b/packages/frontend/src/lib/server/project/create.ts index 740086f..98f10ed 100644 --- a/packages/frontend/src/lib/server/project/create.ts +++ b/packages/frontend/src/lib/server/project/create.ts @@ -1,10 +1,8 @@ import type { ProjectCreateRequest, ProjectRecord } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function create(request: ProjectCreateRequest & ServerQueryInput) { - const { onSuccess, onError, ...body } = request; +export async function create(request: ProjectCreateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/project/create`, { @@ -13,23 +11,18 @@ export async function create(request: ProjectCreateRequest & ServerQueryInput res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to create project (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - if (!data.id) { - toast.error(`failed to create project (${res.status})`); - onError?.(`failed to create project (${res.status})`); - return; - } - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to create project (${res.status})`); + throw new Error(message); } + + const data = (await res.json()) as ProjectRecord; + if (!data.id) { + throw new Error(`failed to create project (${res.status})`); + } + return data; } diff --git a/packages/frontend/src/lib/server/project/delete.ts b/packages/frontend/src/lib/server/project/delete.ts index 4dfe460..e444a7f 100644 --- a/packages/frontend/src/lib/server/project/delete.ts +++ b/packages/frontend/src/lib/server/project/delete.ts @@ -1,15 +1,8 @@ import type { SuccessResponse } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function remove({ - projectId, - onSuccess, - onError, -}: { - projectId: number; -} & ServerQueryInput) { +export async function remove(projectId: number): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/project/delete`, { @@ -23,13 +16,9 @@ export async function remove({ }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to delete project (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to delete project (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/project/update.ts b/packages/frontend/src/lib/server/project/update.ts index 461fe12..f4bb151 100644 --- a/packages/frontend/src/lib/server/project/update.ts +++ b/packages/frontend/src/lib/server/project/update.ts @@ -1,19 +1,8 @@ -import type { ProjectRecord } from "@sprint/shared"; -import { toast } from "sonner"; +import type { ProjectRecord, ProjectUpdateRequest } from "@sprint/shared"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function update({ - projectId, - key, - name, - onSuccess, - onError, -}: { - projectId: number; - key?: string; - name?: string; -} & ServerQueryInput) { +export async function update(input: ProjectUpdateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/project/update`, { @@ -22,22 +11,14 @@ export async function update({ "Content-Type": "application/json", ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, - body: JSON.stringify({ - id: projectId, - key, - name, - }), + body: JSON.stringify(input), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to update project (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to update project (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/sprint/byProject.ts b/packages/frontend/src/lib/server/sprint/byProject.ts index a5c642c..4d53956 100644 --- a/packages/frontend/src/lib/server/sprint/byProject.ts +++ b/packages/frontend/src/lib/server/sprint/byProject.ts @@ -1,14 +1,8 @@ import type { SprintRecord } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function byProject({ - projectId, - onSuccess, - onError, -}: { - projectId: number; -} & ServerQueryInput) { +export async function byProject(projectId: number): Promise { const url = new URL(`${getServerURL()}/sprints/by-project`); url.searchParams.set("projectId", `${projectId}`); @@ -17,10 +11,9 @@ export async function byProject({ }); if (!res.ok) { - const error = await res.text(); - onError?.(error || `failed to get sprints (${res.status})`); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get sprints (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/sprint/create.ts b/packages/frontend/src/lib/server/sprint/create.ts index 1ef6228..f1e3462 100644 --- a/packages/frontend/src/lib/server/sprint/create.ts +++ b/packages/frontend/src/lib/server/sprint/create.ts @@ -1,23 +1,8 @@ -import type { SprintRecord } from "@sprint/shared"; -import { toast } from "sonner"; +import type { SprintCreateRequest, SprintRecord } from "@sprint/shared"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function create({ - projectId, - name, - color, - startDate, - endDate, - onSuccess, - onError, -}: { - projectId: number; - name: string; - color?: string; - startDate: Date; - endDate: Date; -} & ServerQueryInput) { +export async function create(input: SprintCreateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/sprint/create`, { @@ -27,28 +12,20 @@ export async function create({ ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, body: JSON.stringify({ - projectId, - name: name.trim(), - color, - startDate: startDate.toISOString(), - endDate: endDate.toISOString(), + ...input, + name: input.name.trim(), }), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to create sprint (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - if (!data.id) { - toast.error(`failed to create sprint (${res.status})`); - onError?.(`failed to create sprint (${res.status})`); - return; - } - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to create sprint (${res.status})`); + throw new Error(message); } + + const data = (await res.json()) as SprintRecord; + if (!data.id) { + throw new Error(`failed to create sprint (${res.status})`); + } + return data; } diff --git a/packages/frontend/src/lib/server/sprint/delete.ts b/packages/frontend/src/lib/server/sprint/delete.ts index f8be95e..41cf8c1 100644 --- a/packages/frontend/src/lib/server/sprint/delete.ts +++ b/packages/frontend/src/lib/server/sprint/delete.ts @@ -1,15 +1,8 @@ import type { SuccessResponse } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function remove({ - sprintId, - onSuccess, - onError, -}: { - sprintId: number; -} & ServerQueryInput) { +export async function remove(sprintId: number): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/sprint/delete`, { @@ -23,13 +16,9 @@ export async function remove({ }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to delete sprint (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to delete sprint (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/sprint/update.ts b/packages/frontend/src/lib/server/sprint/update.ts index f7c74d9..5cc8fe4 100644 --- a/packages/frontend/src/lib/server/sprint/update.ts +++ b/packages/frontend/src/lib/server/sprint/update.ts @@ -1,23 +1,8 @@ -import type { SprintRecord } from "@sprint/shared"; -import { toast } from "sonner"; +import type { SprintRecord, SprintUpdateRequest } from "@sprint/shared"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function update({ - sprintId, - name, - color, - startDate, - endDate, - onSuccess, - onError, -}: { - sprintId: number; - name?: string; - color?: string; - startDate?: Date; - endDate?: Date; -} & ServerQueryInput) { +export async function update(input: SprintUpdateRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/sprint/update`, { @@ -27,23 +12,16 @@ export async function update({ ...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}), }, body: JSON.stringify({ - id: sprintId, - name: name?.trim(), - color, - startDate: startDate?.toISOString(), - endDate: endDate?.toISOString(), + ...input, + name: input.name?.trim(), }), credentials: "include", }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to update sprint (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to update sprint (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/timer/end.ts b/packages/frontend/src/lib/server/timer/end.ts index 7dfb634..bbc6a54 100644 --- a/packages/frontend/src/lib/server/timer/end.ts +++ b/packages/frontend/src/lib/server/timer/end.ts @@ -1,10 +1,8 @@ import type { TimerEndRequest, TimerState } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function end(request: TimerEndRequest & ServerQueryInput) { - const { onSuccess, onError, ...body } = request; +export async function end(request: TimerEndRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/timer/end`, { @@ -13,18 +11,14 @@ export async function end(request: TimerEndRequest & ServerQueryInput res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to end timer (${res.status})`; - toast.error(message); - onError?.(error); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to end timer (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/timer/get.ts b/packages/frontend/src/lib/server/timer/get.ts index 106427e..5ea04c5 100644 --- a/packages/frontend/src/lib/server/timer/get.ts +++ b/packages/frontend/src/lib/server/timer/get.ts @@ -1,14 +1,8 @@ import type { TimerState } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function get({ - issueId, - onSuccess, - onError, -}: { - issueId: number; -} & ServerQueryInput) { +export async function get(issueId: number): Promise { const url = new URL(`${getServerURL()}/timer/get`); url.searchParams.set("issueId", `${issueId}`); @@ -17,12 +11,9 @@ export async function get({ }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to get timer (${res.status})`; - onError?.(message); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get timer (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/timer/getInactive.ts b/packages/frontend/src/lib/server/timer/getInactive.ts index e7cd7ac..3e80ac5 100644 --- a/packages/frontend/src/lib/server/timer/getInactive.ts +++ b/packages/frontend/src/lib/server/timer/getInactive.ts @@ -1,14 +1,8 @@ import type { TimerState } from "@sprint/shared"; import { getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function getInactive({ - issueId, - onSuccess, - onError, -}: { - issueId: number; -} & ServerQueryInput) { +export async function getInactive(issueId: number): Promise { const url = new URL(`${getServerURL()}/timer/get-inactive`); url.searchParams.set("issueId", `${issueId}`); @@ -17,12 +11,10 @@ export async function getInactive({ }); if (!res.ok) { - const error = await res.json().catch(() => res.text()); - const message = - typeof error === "string" ? error : error.error || `failed to get timers (${res.status})`; - onError?.(message); - } else { - const data = await res.json(); - onSuccess?.(data || [], res); + const message = await getErrorMessage(res, `failed to get timers (${res.status})`); + throw new Error(message); } + + const data = (await res.json()) as TimerState[]; + return data ?? []; } diff --git a/packages/frontend/src/lib/server/timer/list.ts b/packages/frontend/src/lib/server/timer/list.ts index 8b45f70..9b568e8 100644 --- a/packages/frontend/src/lib/server/timer/list.ts +++ b/packages/frontend/src/lib/server/timer/list.ts @@ -1,18 +1,15 @@ import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function list({ - limit, - offset, - onSuccess, - onError, -}: { +type TimerListInput = { limit?: number; offset?: number; -} & ServerQueryInput) { +}; + +export async function list(input: TimerListInput = {}): Promise { const url = new URL(`${getServerURL()}/timers`); - if (limit != null) url.searchParams.set("limit", `${limit}`); - if (offset != null) url.searchParams.set("offset", `${offset}`); + if (input.limit != null) url.searchParams.set("limit", `${input.limit}`); + if (input.offset != null) url.searchParams.set("offset", `${input.offset}`); const csrfToken = getCsrfToken(); const headers: HeadersInit = {}; @@ -24,10 +21,9 @@ export async function list({ }); if (!res.ok) { - const error = await res.text(); - onError?.(error || `failed to get timers (${res.status})`); - } else { - const data = await res.json(); - onSuccess?.(data, res); + const message = await getErrorMessage(res, `failed to get timers (${res.status})`); + throw new Error(message); } + + return res.json(); } diff --git a/packages/frontend/src/lib/server/timer/toggle.ts b/packages/frontend/src/lib/server/timer/toggle.ts index 782a792..9298fe2 100644 --- a/packages/frontend/src/lib/server/timer/toggle.ts +++ b/packages/frontend/src/lib/server/timer/toggle.ts @@ -1,10 +1,8 @@ import type { TimerState, TimerToggleRequest } from "@sprint/shared"; -import { toast } from "sonner"; import { getCsrfToken, getServerURL } from "@/lib/utils"; -import type { ServerQueryInput } from ".."; +import { getErrorMessage } from ".."; -export async function toggle(request: TimerToggleRequest & ServerQueryInput) { - const { onSuccess, onError, ...body } = request; +export async function toggle(request: TimerToggleRequest): Promise { const csrfToken = getCsrfToken(); const res = await fetch(`${getServerURL()}/timer/toggle`, { @@ -13,18 +11,14 @@ export async function toggle(request: TimerToggleRequest & ServerQueryInput