use parseError

This commit is contained in:
Oliver Bryan
2026-01-13 15:34:15 +00:00
parent ca371b1751
commit dc566260d8
10 changed files with 73 additions and 70 deletions

View File

@@ -7,7 +7,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
import { Field } from "@/components/ui/field"; import { Field } from "@/components/ui/field";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { UploadAvatar } from "@/components/upload-avatar"; import { UploadAvatar } from "@/components/upload-avatar";
import { user } from "@/lib/server"; import { parseError, user } from "@/lib/server";
function AccountDialog({ trigger }: { trigger?: ReactNode }) { function AccountDialog({ trigger }: { trigger?: ReactNode }) {
const { user: currentUser, setUser } = useAuthenticatedSession(); const { user: currentUser, setUser } = useAuthenticatedSession();
@@ -41,9 +41,8 @@ function AccountDialog({ trigger }: { trigger?: ReactNode }) {
} }
await user.update({ await user.update({
id: currentUser.id,
name: name.trim(), name: name.trim(),
password: password.trim(), password: password.trim() || undefined,
avatarURL, avatarURL,
onSuccess: (data) => { onSuccess: (data) => {
setError(""); setError("");
@@ -55,10 +54,11 @@ function AccountDialog({ trigger }: { trigger?: ReactNode }) {
dismissible: false, dismissible: false,
}); });
}, },
onError: (error) => { onError: (err) => {
setError(error); const message = parseError(err);
setError(message);
toast.error(`Error updating account: ${error}`, { toast.error(`Error updating account: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },

View File

@@ -11,7 +11,7 @@ import {
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Field } from "@/components/ui/field"; import { Field } from "@/components/ui/field";
import { organisation, user } from "@/lib/server"; import { organisation, parseError, user } from "@/lib/server";
export function AddMemberDialog({ export function AddMemberDialog({
organisationId, organisationId,
@@ -68,11 +68,12 @@ export function AddMemberDialog({
userData = data; userData = data;
userId = data.id; userId = data.id;
}, },
onError: (error) => { onError: (err) => {
setError(error || "user not found"); const message = parseError(err);
setError(message || "user not found");
setSubmitting(false); setSubmitting(false);
toast.error(`Error adding member: ${error}`, { toast.error(`Error adding member: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },
@@ -95,11 +96,12 @@ export function AddMemberDialog({
console.error(actionErr); console.error(actionErr);
} }
}, },
onError: (error) => { onError: (err) => {
setError(error || "failed to add member"); const message = parseError(err);
setError(message || "failed to add member");
setSubmitting(false); setSubmitting(false);
toast.error(`Error adding member: ${error}`, { toast.error(`Error adding member: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },

View File

@@ -23,7 +23,7 @@ import { Field } from "@/components/ui/field";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { SelectTrigger } from "@/components/ui/select"; import { SelectTrigger } from "@/components/ui/select";
import { UserSelect } from "@/components/user-select"; import { UserSelect } from "@/components/user-select";
import { issue } from "@/lib/server"; import { issue, parseError } from "@/lib/server";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { SprintSelect } from "./sprint-select"; import { SprintSelect } from "./sprint-select";
@@ -116,16 +116,17 @@ export function CreateIssue({
console.error(actionErr); console.error(actionErr);
} }
}, },
onError: async (error) => { onError: async (err) => {
setError(error); const message = parseError(err);
setError(message);
setSubmitting(false); setSubmitting(false);
toast.error(`Error creating issue: ${error}`, { toast.error(`Error creating issue: ${message}`, {
dismissible: false, dismissible: false,
}); });
try { try {
await errorAction?.(error); await errorAction?.(message);
} catch (actionErr) { } catch (actionErr) {
console.error(actionErr); console.error(actionErr);
} }

View File

@@ -17,7 +17,7 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Field } from "@/components/ui/field"; import { Field } from "@/components/ui/field";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { organisation } from "@/lib/server"; import { organisation, parseError } from "@/lib/server";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const slugify = (value: string) => const slugify = (value: string) =>
@@ -85,7 +85,6 @@ export function CreateOrganisation({
name, name,
slug, slug,
description, description,
userId: user.id,
onSuccess: async (data) => { onSuccess: async (data) => {
setOpen(false); setOpen(false);
reset(); reset();
@@ -96,10 +95,11 @@ export function CreateOrganisation({
} }
}, },
onError: async (err) => { onError: async (err) => {
setError(err || "failed to create organisation"); const message = parseError(err);
setError(message || "failed to create organisation");
setSubmitting(false); setSubmitting(false);
try { try {
await errorAction?.(err || "failed to create organisation"); await errorAction?.(message || "failed to create organisation");
} catch (actionErr) { } catch (actionErr) {
console.error(actionErr); console.error(actionErr);
} }

View File

@@ -13,7 +13,7 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Field } from "@/components/ui/field"; import { Field } from "@/components/ui/field";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { project } from "@/lib/server"; import { parseError, project } from "@/lib/server";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const keyify = (value: string) => const keyify = (value: string) =>
@@ -86,24 +86,24 @@ export function CreateProject({
await project.create({ await project.create({
key, key,
name, name,
creatorId: user.id,
organisationId, organisationId,
onSuccess: async (data) => { onSuccess: async (data) => {
const project = data as ProjectRecord; const proj = data as ProjectRecord;
setOpen(false); setOpen(false);
reset(); reset();
try { try {
await completeAction?.(project); await completeAction?.(proj);
} catch (actionErr) { } catch (actionErr) {
console.error(actionErr); console.error(actionErr);
} }
}, },
onError: (error) => { onError: (err) => {
setError(error); const message = parseError(err);
setError(message);
setSubmitting(false); setSubmitting(false);
toast.error(`Error creating project: ${error}`, { toast.error(`Error creating project: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },

View File

@@ -16,7 +16,7 @@ import {
import { Field } from "@/components/ui/field"; import { Field } from "@/components/ui/field";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { sprint } from "@/lib/server"; import { parseError, sprint } from "@/lib/server";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const SPRINT_NAME_MAX_LENGTH = 64; const SPRINT_NAME_MAX_LENGTH = 64;
@@ -135,11 +135,12 @@ export function CreateSprint({
console.error(actionErr); console.error(actionErr);
} }
}, },
onError: (error) => { onError: (err) => {
setError(error); const message = parseError(err);
setError(message);
setSubmitting(false); setSubmitting(false);
toast.error(`Error creating sprint: ${error}`, { toast.error(`Error creating sprint: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },

View File

@@ -1,7 +1,7 @@
import type { TimerState } from "@issue/shared"; import type { TimerState } from "@issue/shared";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { timer } from "@/lib/server"; import { parseError, timer } from "@/lib/server";
import { cn, formatTime } from "@/lib/utils"; import { cn, formatTime } from "@/lib/utils";
export function IssueTimer({ issueId, onEnd }: { issueId: number; onEnd?: (data: TimerState) => void }) { export function IssueTimer({ issueId, onEnd }: { issueId: number; onEnd?: (data: TimerState) => void }) {
@@ -19,7 +19,7 @@ export function IssueTimer({ issueId, onEnd }: { issueId: number; onEnd?: (data:
setDisplayTime(data.workTimeMs); setDisplayTime(data.workTimeMs);
} }
}, },
onError: setError, onError: (err) => setError(parseError(err)),
}); });
}, [issueId]); }, [issueId]);
@@ -41,11 +41,13 @@ export function IssueTimer({ issueId, onEnd }: { issueId: number; onEnd?: (data:
timer.toggle({ timer.toggle({
issueId, issueId,
onSuccess: (data) => { onSuccess: (data) => {
if (data) {
setTimerState(data); setTimerState(data);
setDisplayTime(data.workTimeMs); setDisplayTime(data.workTimeMs);
}
setError(null); setError(null);
}, },
onError: setError, onError: (err) => setError(parseError(err)),
}); });
}; };
@@ -53,12 +55,14 @@ export function IssueTimer({ issueId, onEnd }: { issueId: number; onEnd?: (data:
timer.end({ timer.end({
issueId, issueId,
onSuccess: (data) => { onSuccess: (data) => {
if (data) {
setTimerState(data); setTimerState(data);
setDisplayTime(data.workTimeMs); setDisplayTime(data.workTimeMs);
setError(null);
onEnd?.(data); onEnd?.(data);
}
setError(null);
}, },
onError: setError, onError: (err) => setError(parseError(err)),
}); });
}; };

View File

@@ -1,7 +1,7 @@
import type { TimerState } from "@issue/shared"; import type { TimerState } from "@issue/shared";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { timer } from "@/lib/server"; import { parseError, timer } from "@/lib/server";
import { formatTime } from "@/lib/utils"; import { formatTime } from "@/lib/utils";
const FALLBACK_TIME = "--:--:--"; const FALLBACK_TIME = "--:--:--";
@@ -25,11 +25,12 @@ export function TimerDisplay({ issueId }: { issueId: number }) {
setWorkTimeMs(data?.workTimeMs ?? 0); setWorkTimeMs(data?.workTimeMs ?? 0);
setError(null); setError(null);
}, },
onError: (error) => { onError: (err) => {
if (!isMounted) return; if (!isMounted) return;
setError(error); const message = parseError(err);
setError(message);
toast.error(`Error fetching timer data: ${error}`, { toast.error(`Error fetching timer data: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },
@@ -39,19 +40,19 @@ export function TimerDisplay({ issueId }: { issueId: number }) {
issueId, issueId,
onSuccess: (data) => { onSuccess: (data) => {
if (!isMounted) return; if (!isMounted) return;
const sessions = (data ?? []) as TimerState[]; const totalWorkTime = data.reduce(
const totalWorkTime = sessions.reduce(
(total, session) => total + (session?.workTimeMs ?? 0), (total, session) => total + (session?.workTimeMs ?? 0),
0, 0,
); );
setInactiveWorkTimeMs(totalWorkTime); setInactiveWorkTimeMs(totalWorkTime);
setError(null); setError(null);
}, },
onError: (error) => { onError: (err) => {
if (!isMounted) return; if (!isMounted) return;
setError(error); const message = parseError(err);
setError(message);
toast.error(`Error fetching timer data: ${error}`, { toast.error(`Error fetching timer data: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },

View File

@@ -4,7 +4,7 @@ import { toast } from "sonner";
import Avatar from "@/components/avatar"; import Avatar from "@/components/avatar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { user } from "@/lib/server"; import { parseError, user } from "@/lib/server";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export function UploadAvatar({ export function UploadAvatar({
@@ -49,11 +49,12 @@ export function UploadAvatar({
}, },
); );
}, },
onError: (error) => { onError: (err) => {
setError(error); const message = parseError(err);
setError(message);
setUploading(false); setUploading(false);
toast.error(`Error uploading avatar: ${error}`, { toast.error(`Error uploading avatar: ${message}`, {
dismissible: false, dismissible: false,
}); });
}, },

View File

@@ -2,7 +2,6 @@
import type { import type {
IssueResponse, IssueResponse,
OrganisationMemberResponse,
OrganisationResponse, OrganisationResponse,
ProjectRecord, ProjectRecord,
ProjectResponse, ProjectResponse,
@@ -107,25 +106,21 @@ export default function App() {
const refetchOrganisations = async (options?: { selectOrganisationId?: number }) => { const refetchOrganisations = async (options?: { selectOrganisationId?: number }) => {
try { try {
await organisation.byUser({ await organisation.byUser({
userId: user.id,
onSuccess: (data) => { onSuccess: (data) => {
const organisations = data as OrganisationResponse[]; data.sort((a, b) => a.Organisation.name.localeCompare(b.Organisation.name));
organisations.sort((a, b) => a.Organisation.name.localeCompare(b.Organisation.name)); setOrganisations(data);
setOrganisations(organisations);
let selected: OrganisationResponse | null = null; let selected: OrganisationResponse | null = null;
if (options?.selectOrganisationId) { if (options?.selectOrganisationId) {
const created = organisations.find( const created = data.find((o) => o.Organisation.id === options.selectOrganisationId);
(o) => o.Organisation.id === options.selectOrganisationId,
);
if (created) { if (created) {
selected = created; selected = created;
} }
} else { } else {
const deepLinkState = deepLinkStateRef.current; const deepLinkState = deepLinkStateRef.current;
if (deepLinkParams.orgSlug && !deepLinkState.appliedOrg) { if (deepLinkParams.orgSlug && !deepLinkState.appliedOrg) {
const match = organisations.find( const match = data.find(
(org) => org.Organisation.slug.toLowerCase() === deepLinkParams.orgSlug, (org) => org.Organisation.slug.toLowerCase() === deepLinkParams.orgSlug,
); );
deepLinkState.appliedOrg = true; deepLinkState.appliedOrg = true;
@@ -139,9 +134,7 @@ export default function App() {
if (!selected) { if (!selected) {
const savedId = localStorage.getItem("selectedOrganisationId"); const savedId = localStorage.getItem("selectedOrganisationId");
if (savedId) { if (savedId) {
const saved = organisations.find( const saved = data.find((o) => o.Organisation.id === Number(savedId));
(o) => o.Organisation.id === Number(savedId),
);
if (saved) { if (saved) {
selected = saved; selected = saved;
} }
@@ -150,7 +143,7 @@ export default function App() {
} }
if (!selected) { if (!selected) {
selected = organisations[0] || null; selected = data[0] || null;
} }
setSelectedOrganisation(selected); setSelectedOrganisation(selected);
@@ -260,7 +253,7 @@ export default function App() {
try { try {
await organisation.members({ await organisation.members({
organisationId, organisationId,
onSuccess: (data: OrganisationMemberResponse[]) => { onSuccess: (data) => {
setMembers(data.map((m) => m.User)); setMembers(data.map((m) => m.User));
}, },
onError: (error) => { onError: (error) => {
@@ -282,7 +275,7 @@ export default function App() {
try { try {
await sprint.byProject({ await sprint.byProject({
projectId, projectId,
onSuccess: (data: SprintRecord[]) => { onSuccess: (data) => {
setSprints(data); setSprints(data);
}, },
onError: (error) => { onError: (error) => {