mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
improved error messages
This commit is contained in:
@@ -98,7 +98,9 @@ export default function LogInForm() {
|
|||||||
}
|
}
|
||||||
// bad request (probably a bad user input)
|
// bad request (probably a bad user input)
|
||||||
else if (res.status === 400) {
|
else if (res.status === 400) {
|
||||||
setError(await res.text());
|
const data = await res.json();
|
||||||
|
const firstDetail = data.details ? Object.values(data.details).flat().find(Boolean) : "";
|
||||||
|
setError(firstDetail || data.error || "Bad request");
|
||||||
} else {
|
} else {
|
||||||
setError("An unknown error occured.");
|
setError("An unknown error occured.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,25 +24,25 @@ export type ApiError = z.infer<typeof ApiErrorSchema>;
|
|||||||
// auth schemas
|
// auth schemas
|
||||||
|
|
||||||
export const LoginRequestSchema = z.object({
|
export const LoginRequestSchema = z.object({
|
||||||
username: z.string().min(1, "username is required").max(USER_USERNAME_MAX_LENGTH),
|
username: z.string().min(1, "Username is required").max(USER_USERNAME_MAX_LENGTH),
|
||||||
password: z.string().min(1, "password is required"),
|
password: z.string().min(1, "Password is required"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type LoginRequest = z.infer<typeof LoginRequestSchema>;
|
export type LoginRequest = z.infer<typeof LoginRequestSchema>;
|
||||||
|
|
||||||
export const RegisterRequestSchema = z.object({
|
export const RegisterRequestSchema = z.object({
|
||||||
name: z.string().min(1, "name is required").max(USER_NAME_MAX_LENGTH),
|
name: z.string().min(1, "Name is required").max(USER_NAME_MAX_LENGTH),
|
||||||
username: z
|
username: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "username is required")
|
.min(1, "Username is required")
|
||||||
.max(USER_USERNAME_MAX_LENGTH)
|
.max(USER_USERNAME_MAX_LENGTH)
|
||||||
.regex(/^[a-zA-Z0-9_-]+$/, "username can only contain letters, numbers, underscores, and hyphens"),
|
.regex(/^[a-zA-Z0-9_-]+$/, "Username can only contain letters, numbers, underscores, and hyphens"),
|
||||||
password: z
|
password: z
|
||||||
.string()
|
.string()
|
||||||
.min(8, "password must be at least 8 characters")
|
.min(8, "Password must be at least 8 characters")
|
||||||
.regex(/[A-Z]/, "password must contain an uppercase letter")
|
.regex(/[A-Z]/, "Password must contain an uppercase letter")
|
||||||
.regex(/[a-z]/, "password must contain a lowercase letter")
|
.regex(/[a-z]/, "Password must contain a lowercase letter")
|
||||||
.regex(/[0-9]/, "password must contain a number"),
|
.regex(/[0-9]/, "Password must contain a number"),
|
||||||
avatarURL: z.string().url().nullable(),
|
avatarURL: z.string().url().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ export type AuthResponse = z.infer<typeof AuthResponseSchema>;
|
|||||||
|
|
||||||
export const IssueCreateRequestSchema = z.object({
|
export const IssueCreateRequestSchema = z.object({
|
||||||
projectId: z.number().int().positive("projectId must be a positive integer"),
|
projectId: z.number().int().positive("projectId must be a positive integer"),
|
||||||
title: z.string().min(1, "title is required").max(ISSUE_TITLE_MAX_LENGTH),
|
title: z.string().min(1, "Title is required").max(ISSUE_TITLE_MAX_LENGTH),
|
||||||
description: z.string().max(ISSUE_DESCRIPTION_MAX_LENGTH).default(""),
|
description: z.string().max(ISSUE_DESCRIPTION_MAX_LENGTH).default(""),
|
||||||
status: z.string().max(ISSUE_STATUS_MAX_LENGTH).optional(),
|
status: z.string().max(ISSUE_STATUS_MAX_LENGTH).optional(),
|
||||||
assigneeIds: z.array(z.number().int().positive()).optional(),
|
assigneeIds: z.array(z.number().int().positive()).optional(),
|
||||||
@@ -75,7 +75,7 @@ export type IssueCreateRequest = z.infer<typeof IssueCreateRequestSchema>;
|
|||||||
|
|
||||||
export const IssueUpdateRequestSchema = z.object({
|
export const IssueUpdateRequestSchema = z.object({
|
||||||
id: z.number().int().positive("id must be a positive integer"),
|
id: z.number().int().positive("id must be a positive integer"),
|
||||||
title: z.string().min(1).max(ISSUE_TITLE_MAX_LENGTH).optional(),
|
title: z.string().min(1, "Title must be at least 1 character").max(ISSUE_TITLE_MAX_LENGTH).optional(),
|
||||||
description: z.string().max(ISSUE_DESCRIPTION_MAX_LENGTH).optional(),
|
description: z.string().max(ISSUE_DESCRIPTION_MAX_LENGTH).optional(),
|
||||||
status: z.string().max(ISSUE_STATUS_MAX_LENGTH).optional(),
|
status: z.string().max(ISSUE_STATUS_MAX_LENGTH).optional(),
|
||||||
assigneeIds: z.array(z.number().int().positive()).nullable().optional(),
|
assigneeIds: z.array(z.number().int().positive()).nullable().optional(),
|
||||||
@@ -98,7 +98,7 @@ export type IssuesByProjectQuery = z.infer<typeof IssuesByProjectQuerySchema>;
|
|||||||
|
|
||||||
export const IssuesStatusCountQuerySchema = z.object({
|
export const IssuesStatusCountQuerySchema = z.object({
|
||||||
organisationId: z.coerce.number().int().positive("organisationId must be a positive integer"),
|
organisationId: z.coerce.number().int().positive("organisationId must be a positive integer"),
|
||||||
status: z.string().min(1, "status is required").max(ISSUE_STATUS_MAX_LENGTH),
|
status: z.string().min(1, "Status is required").max(ISSUE_STATUS_MAX_LENGTH),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type IssuesStatusCountQuery = z.infer<typeof IssuesStatusCountQuerySchema>;
|
export type IssuesStatusCountQuery = z.infer<typeof IssuesStatusCountQuerySchema>;
|
||||||
@@ -114,12 +114,12 @@ export type IssuesReplaceStatusRequest = z.infer<typeof IssuesReplaceStatusReque
|
|||||||
// organisation schemas
|
// organisation schemas
|
||||||
|
|
||||||
export const OrgCreateRequestSchema = z.object({
|
export const OrgCreateRequestSchema = z.object({
|
||||||
name: z.string().min(1, "name is required").max(ORG_NAME_MAX_LENGTH),
|
name: z.string().min(1, "Name is required").max(ORG_NAME_MAX_LENGTH),
|
||||||
slug: z
|
slug: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "slug is required")
|
.min(1, "Slug is required")
|
||||||
.max(ORG_SLUG_MAX_LENGTH)
|
.max(ORG_SLUG_MAX_LENGTH)
|
||||||
.regex(/^[a-z0-9-]+$/, "slug can only contain lowercase letters, numbers, and hyphens"),
|
.regex(/^[a-z0-9-]+$/, "Slug can only contain lowercase letters, numbers, and hyphens"),
|
||||||
description: z.string().max(ORG_DESCRIPTION_MAX_LENGTH).optional(),
|
description: z.string().max(ORG_DESCRIPTION_MAX_LENGTH).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,20 +127,20 @@ export type OrgCreateRequest = z.infer<typeof OrgCreateRequestSchema>;
|
|||||||
|
|
||||||
export const OrgUpdateRequestSchema = z.object({
|
export const OrgUpdateRequestSchema = z.object({
|
||||||
id: z.number().int().positive("id must be a positive integer"),
|
id: z.number().int().positive("id must be a positive integer"),
|
||||||
name: z.string().min(1).max(ORG_NAME_MAX_LENGTH).optional(),
|
name: z.string().min(1, "Name must be at least 1 character").max(ORG_NAME_MAX_LENGTH).optional(),
|
||||||
description: z.string().max(ORG_DESCRIPTION_MAX_LENGTH).optional(),
|
description: z.string().max(ORG_DESCRIPTION_MAX_LENGTH).optional(),
|
||||||
slug: z
|
slug: z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1, "Slug must be at least 1 character")
|
||||||
.max(ORG_SLUG_MAX_LENGTH)
|
.max(ORG_SLUG_MAX_LENGTH)
|
||||||
.regex(/^[a-z0-9-]+$/)
|
.regex(/^[a-z0-9-]+$/)
|
||||||
.optional(),
|
.optional(),
|
||||||
statuses: z
|
statuses: z
|
||||||
.record(z.string())
|
.record(z.string())
|
||||||
.refine((obj) => Object.keys(obj).length > 0, "statuses must have at least one entry")
|
.refine((obj) => Object.keys(obj).length > 0, "Statuses must have at least one entry")
|
||||||
.refine(
|
.refine(
|
||||||
(obj) => Object.keys(obj).every((key) => key.length <= ISSUE_STATUS_MAX_LENGTH),
|
(obj) => Object.keys(obj).every((key) => key.length <= ISSUE_STATUS_MAX_LENGTH),
|
||||||
`status keys must be <= ${ISSUE_STATUS_MAX_LENGTH} characters`,
|
`Status keys must be <= ${ISSUE_STATUS_MAX_LENGTH} characters`,
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
@@ -191,11 +191,11 @@ export type OrgUpdateMemberRoleRequest = z.infer<typeof OrgUpdateMemberRoleReque
|
|||||||
// project schemas
|
// project schemas
|
||||||
|
|
||||||
export const ProjectCreateRequestSchema = z.object({
|
export const ProjectCreateRequestSchema = z.object({
|
||||||
name: z.string().min(1, "name is required").max(PROJECT_NAME_MAX_LENGTH),
|
name: z.string().min(1, "Name is required").max(PROJECT_NAME_MAX_LENGTH),
|
||||||
key: z
|
key: z
|
||||||
.string()
|
.string()
|
||||||
.length(4, "key must be exactly 4 characters")
|
.length(4, "Key must be exactly 4 characters")
|
||||||
.regex(/^[A-Z]{4}$/, "key must be 4 uppercase letters"),
|
.regex(/^[A-Z]{4}$/, "Key must be 4 uppercase letters"),
|
||||||
organisationId: z.number().int().positive("organisationId must be a positive integer"),
|
organisationId: z.number().int().positive("organisationId must be a positive integer"),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,11 +203,11 @@ export type ProjectCreateRequest = z.infer<typeof ProjectCreateRequestSchema>;
|
|||||||
|
|
||||||
export const ProjectUpdateRequestSchema = z.object({
|
export const ProjectUpdateRequestSchema = z.object({
|
||||||
id: z.number().int().positive("id must be a positive integer"),
|
id: z.number().int().positive("id must be a positive integer"),
|
||||||
name: z.string().min(1).max(PROJECT_NAME_MAX_LENGTH).optional(),
|
name: z.string().min(1, "Name must be at least 1 character").max(PROJECT_NAME_MAX_LENGTH).optional(),
|
||||||
key: z
|
key: z
|
||||||
.string()
|
.string()
|
||||||
.length(4)
|
.length(4, "Key must be exactly 4 characters")
|
||||||
.regex(/^[A-Z]{4}$/)
|
.regex(/^[A-Z]{4}$/, "Key must be 4 uppercase letters")
|
||||||
.optional(),
|
.optional(),
|
||||||
creatorId: z.number().int().positive().optional(),
|
creatorId: z.number().int().positive().optional(),
|
||||||
organisationId: z.number().int().positive().optional(),
|
organisationId: z.number().int().positive().optional(),
|
||||||
@@ -244,16 +244,16 @@ export type ProjectByCreatorQuery = z.infer<typeof ProjectByCreatorQuerySchema>;
|
|||||||
export const SprintCreateRequestSchema = z
|
export const SprintCreateRequestSchema = z
|
||||||
.object({
|
.object({
|
||||||
projectId: z.number().int().positive("projectId must be a positive integer"),
|
projectId: z.number().int().positive("projectId must be a positive integer"),
|
||||||
name: z.string().min(1, "name is required").max(64),
|
name: z.string().min(1, "Name is required").max(64, "Name must be at most 64 characters"),
|
||||||
color: z
|
color: z
|
||||||
.string()
|
.string()
|
||||||
.regex(/^#[0-9a-fA-F]{6}$/, "color must be a valid hex color")
|
.regex(/^#[0-9a-fA-F]{6}$/, "Color must be a valid hex color")
|
||||||
.optional(),
|
.optional(),
|
||||||
startDate: z.string().datetime("startDate must be a valid ISO datetime"),
|
startDate: z.string().datetime("Start date must be a valid date"),
|
||||||
endDate: z.string().datetime("endDate must be a valid ISO datetime"),
|
endDate: z.string().datetime("End date must be a valid date"),
|
||||||
})
|
})
|
||||||
.refine((data) => new Date(data.startDate) <= new Date(data.endDate), {
|
.refine((data) => new Date(data.startDate) <= new Date(data.endDate), {
|
||||||
message: "endDate must be after startDate",
|
message: "End date must be after start date",
|
||||||
path: ["endDate"],
|
path: ["endDate"],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -288,13 +288,13 @@ export type TimerGetQuery = z.infer<typeof TimerGetQuerySchema>;
|
|||||||
// user schemas
|
// user schemas
|
||||||
|
|
||||||
export const UserUpdateRequestSchema = z.object({
|
export const UserUpdateRequestSchema = z.object({
|
||||||
name: z.string().min(1).max(USER_NAME_MAX_LENGTH).optional(),
|
name: z.string().min(1, "Name must be at least 1 character").max(USER_NAME_MAX_LENGTH).optional(),
|
||||||
password: z
|
password: z
|
||||||
.string()
|
.string()
|
||||||
.min(8)
|
.min(8, "Password must be at least 8 characters")
|
||||||
.regex(/[A-Z]/, "password must contain an uppercase letter")
|
.regex(/[A-Z]/, "Password must contain an uppercase letter")
|
||||||
.regex(/[a-z]/, "password must contain a lowercase letter")
|
.regex(/[a-z]/, "Password must contain a lowercase letter")
|
||||||
.regex(/[0-9]/, "password must contain a number")
|
.regex(/[0-9]/, "Password must contain a number")
|
||||||
.optional(),
|
.optional(),
|
||||||
avatarURL: z.string().url().nullable().optional(),
|
avatarURL: z.string().url().nullable().optional(),
|
||||||
});
|
});
|
||||||
@@ -302,7 +302,7 @@ export const UserUpdateRequestSchema = z.object({
|
|||||||
export type UserUpdateRequest = z.infer<typeof UserUpdateRequestSchema>;
|
export type UserUpdateRequest = z.infer<typeof UserUpdateRequestSchema>;
|
||||||
|
|
||||||
export const UserByUsernameQuerySchema = z.object({
|
export const UserByUsernameQuerySchema = z.object({
|
||||||
username: z.string().min(1, "username is required"),
|
username: z.string().min(1, "Username is required"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UserByUsernameQuery = z.infer<typeof UserByUsernameQuerySchema>;
|
export type UserByUsernameQuery = z.infer<typeof UserByUsernameQuerySchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user