mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 10:33:01 +00:00
frontend indentation set to 2
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 5 * 60 * 1000, // 5 mins
|
||||
gcTime: 10 * 60 * 1000, // 10 mins
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
},
|
||||
mutations: {
|
||||
retry: 0,
|
||||
},
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 5 * 60 * 1000, // 5 mins
|
||||
gcTime: 10 * 60 * 1000, // 10 mins
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
},
|
||||
mutations: {
|
||||
retry: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,31 +5,31 @@ import { useOrganisations } from "@/lib/query/hooks/organisations";
|
||||
import { useProjects } from "@/lib/query/hooks/projects";
|
||||
|
||||
export function useSelectedOrganisation() {
|
||||
const { selectedOrganisationId } = useSelection();
|
||||
const { data: organisations = [] } = useOrganisations();
|
||||
const { selectedOrganisationId } = useSelection();
|
||||
const { data: organisations = [] } = useOrganisations();
|
||||
|
||||
return useMemo(
|
||||
() => organisations.find((org) => org.Organisation.id === selectedOrganisationId) ?? null,
|
||||
[organisations, selectedOrganisationId],
|
||||
);
|
||||
return useMemo(
|
||||
() => organisations.find((org) => org.Organisation.id === selectedOrganisationId) ?? null,
|
||||
[organisations, selectedOrganisationId],
|
||||
);
|
||||
}
|
||||
|
||||
export function useSelectedProject() {
|
||||
const { selectedOrganisationId, selectedProjectId } = useSelection();
|
||||
const { data: projects = [] } = useProjects(selectedOrganisationId);
|
||||
const { selectedOrganisationId, selectedProjectId } = useSelection();
|
||||
const { data: projects = [] } = useProjects(selectedOrganisationId);
|
||||
|
||||
return useMemo(
|
||||
() => projects.find((project) => project.Project.id === selectedProjectId) ?? null,
|
||||
[projects, selectedProjectId],
|
||||
);
|
||||
return useMemo(
|
||||
() => projects.find((project) => project.Project.id === selectedProjectId) ?? null,
|
||||
[projects, selectedProjectId],
|
||||
);
|
||||
}
|
||||
|
||||
export function useSelectedIssue() {
|
||||
const { selectedProjectId, selectedIssueId } = useSelection();
|
||||
const { data: issues = [] } = useIssues(selectedProjectId);
|
||||
const { selectedProjectId, selectedIssueId } = useSelection();
|
||||
const { data: issues = [] } = useIssues(selectedProjectId);
|
||||
|
||||
return useMemo(
|
||||
() => issues.find((issue) => issue.Issue.id === selectedIssueId) ?? null,
|
||||
[issues, selectedIssueId],
|
||||
);
|
||||
return useMemo(
|
||||
() => issues.find((issue) => issue.Issue.id === selectedIssueId) ?? null,
|
||||
[issues, selectedIssueId],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
import type {
|
||||
IssueCreateRequest,
|
||||
IssueRecord,
|
||||
IssueResponse,
|
||||
IssuesReplaceStatusRequest,
|
||||
IssueUpdateRequest,
|
||||
StatusCountResponse,
|
||||
SuccessResponse,
|
||||
IssueCreateRequest,
|
||||
IssueRecord,
|
||||
IssueResponse,
|
||||
IssuesReplaceStatusRequest,
|
||||
IssueUpdateRequest,
|
||||
StatusCountResponse,
|
||||
SuccessResponse,
|
||||
} from "@sprint/shared";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { queryKeys } from "@/lib/query/keys";
|
||||
import { issue } from "@/lib/server";
|
||||
|
||||
export function useIssues(projectId?: number | null) {
|
||||
return useQuery<IssueResponse[]>({
|
||||
queryKey: queryKeys.issues.byProject(projectId ?? 0),
|
||||
queryFn: () => issue.byProject(projectId ?? 0),
|
||||
enabled: Boolean(projectId),
|
||||
});
|
||||
return useQuery<IssueResponse[]>({
|
||||
queryKey: queryKeys.issues.byProject(projectId ?? 0),
|
||||
queryFn: () => issue.byProject(projectId ?? 0),
|
||||
enabled: Boolean(projectId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useIssueStatusCount(organisationId?: number | null, status?: string | null) {
|
||||
return useQuery<StatusCountResponse>({
|
||||
queryKey: queryKeys.issues.statusCount(organisationId ?? 0, status ?? ""),
|
||||
queryFn: () => issue.statusCount(organisationId ?? 0, status ?? ""),
|
||||
enabled: Boolean(organisationId && status),
|
||||
});
|
||||
return useQuery<StatusCountResponse>({
|
||||
queryKey: queryKeys.issues.statusCount(organisationId ?? 0, status ?? ""),
|
||||
queryFn: () => issue.statusCount(organisationId ?? 0, status ?? ""),
|
||||
enabled: Boolean(organisationId && status),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateIssue() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<IssueRecord, Error, IssueCreateRequest>({
|
||||
mutationKey: ["issues", "create"],
|
||||
mutationFn: issue.create,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.issues.byProject(variables.projectId),
|
||||
});
|
||||
},
|
||||
});
|
||||
return useMutation<IssueRecord, Error, IssueCreateRequest>({
|
||||
mutationKey: ["issues", "create"],
|
||||
mutationFn: issue.create,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.issues.byProject(variables.projectId),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateIssue() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<IssueRecord, Error, IssueUpdateRequest>({
|
||||
mutationKey: ["issues", "update"],
|
||||
mutationFn: issue.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
return useMutation<IssueRecord, Error, IssueUpdateRequest>({
|
||||
mutationKey: ["issues", "update"],
|
||||
mutationFn: issue.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteIssue() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<SuccessResponse, Error, number>({
|
||||
mutationKey: ["issues", "delete"],
|
||||
mutationFn: issue.delete,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
return useMutation<SuccessResponse, Error, number>({
|
||||
mutationKey: ["issues", "delete"],
|
||||
mutationFn: issue.delete,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReplaceIssueStatus() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<unknown, Error, IssuesReplaceStatusRequest>({
|
||||
mutationKey: ["issues", "replace-status"],
|
||||
mutationFn: issue.replaceStatus,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
return useMutation<unknown, Error, IssuesReplaceStatusRequest>({
|
||||
mutationKey: ["issues", "replace-status"],
|
||||
mutationFn: issue.replaceStatus,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
import type {
|
||||
OrgAddMemberRequest,
|
||||
OrganisationMemberRecord,
|
||||
OrganisationMemberResponse,
|
||||
OrgAddMemberRequest,
|
||||
OrganisationMemberRecord,
|
||||
OrganisationMemberResponse,
|
||||
} from "@sprint/shared";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { queryKeys } from "@/lib/query/keys";
|
||||
import { organisation } from "@/lib/server";
|
||||
|
||||
export function useOrganisations() {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.organisations.byUser(),
|
||||
queryFn: organisation.byUser,
|
||||
});
|
||||
return useQuery({
|
||||
queryKey: queryKeys.organisations.byUser(),
|
||||
queryFn: organisation.byUser,
|
||||
});
|
||||
}
|
||||
|
||||
export function useOrganisationMembers(organisationId?: number | null) {
|
||||
return useQuery<OrganisationMemberResponse[]>({
|
||||
queryKey: queryKeys.organisations.members(organisationId ?? 0),
|
||||
queryFn: () => organisation.members(organisationId ?? 0),
|
||||
enabled: Boolean(organisationId),
|
||||
});
|
||||
return useQuery<OrganisationMemberResponse[]>({
|
||||
queryKey: queryKeys.organisations.members(organisationId ?? 0),
|
||||
queryFn: () => organisation.members(organisationId ?? 0),
|
||||
enabled: Boolean(organisationId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateOrganisation() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "create"],
|
||||
mutationFn: organisation.create,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "create"],
|
||||
mutationFn: organisation.create,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateOrganisation() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "update"],
|
||||
mutationFn: organisation.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "update"],
|
||||
mutationFn: organisation.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteOrganisation() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "delete"],
|
||||
mutationFn: organisation.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "delete"],
|
||||
mutationFn: organisation.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAddOrganisationMember() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<OrganisationMemberRecord, Error, OrgAddMemberRequest>({
|
||||
mutationKey: ["organisations", "members", "add"],
|
||||
mutationFn: organisation.addMember,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.organisations.members(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
return useMutation<OrganisationMemberRecord, Error, OrgAddMemberRequest>({
|
||||
mutationKey: ["organisations", "members", "add"],
|
||||
mutationFn: organisation.addMember,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.organisations.members(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveOrganisationMember() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "members", "remove"],
|
||||
mutationFn: organisation.removeMember,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.organisations.members(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "members", "remove"],
|
||||
mutationFn: organisation.removeMember,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.organisations.members(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateOrganisationMemberRole() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "members", "update-role"],
|
||||
mutationFn: organisation.updateMemberRole,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.organisations.members(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "members", "update-role"],
|
||||
mutationFn: organisation.updateMemberRole,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.organisations.members(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUploadOrganisationIcon() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<string, Error, { file: File; organisationId: number }>({
|
||||
mutationKey: ["organisations", "upload-icon"],
|
||||
mutationFn: ({ file, organisationId }) => organisation.uploadIcon(file, organisationId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
return useMutation<string, Error, { file: File; organisationId: number }>({
|
||||
mutationKey: ["organisations", "upload-icon"],
|
||||
mutationFn: ({ file, organisationId }) => organisation.uploadIcon(file, organisationId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
import type {
|
||||
ProjectCreateRequest,
|
||||
ProjectRecord,
|
||||
ProjectResponse,
|
||||
ProjectUpdateRequest,
|
||||
ProjectCreateRequest,
|
||||
ProjectRecord,
|
||||
ProjectResponse,
|
||||
ProjectUpdateRequest,
|
||||
} from "@sprint/shared";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { queryKeys } from "@/lib/query/keys";
|
||||
import { project } from "@/lib/server";
|
||||
|
||||
export function useProjects(organisationId?: number | null) {
|
||||
return useQuery<ProjectResponse[]>({
|
||||
queryKey: queryKeys.projects.byOrganisation(organisationId ?? 0),
|
||||
queryFn: () => project.byOrganisation(organisationId ?? 0),
|
||||
enabled: Boolean(organisationId),
|
||||
});
|
||||
return useQuery<ProjectResponse[]>({
|
||||
queryKey: queryKeys.projects.byOrganisation(organisationId ?? 0),
|
||||
queryFn: () => project.byOrganisation(organisationId ?? 0),
|
||||
enabled: Boolean(organisationId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateProject() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ProjectRecord, Error, ProjectCreateRequest>({
|
||||
mutationKey: ["projects", "create"],
|
||||
mutationFn: project.create,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.projects.byOrganisation(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
return useMutation<ProjectRecord, Error, ProjectCreateRequest>({
|
||||
mutationKey: ["projects", "create"],
|
||||
mutationFn: project.create,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.projects.byOrganisation(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateProject() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ProjectRecord, Error, ProjectUpdateRequest>({
|
||||
mutationKey: ["projects", "update"],
|
||||
mutationFn: project.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.projects.all });
|
||||
},
|
||||
});
|
||||
return useMutation<ProjectRecord, Error, ProjectUpdateRequest>({
|
||||
mutationKey: ["projects", "update"],
|
||||
mutationFn: project.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.projects.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteProject() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["projects", "delete"],
|
||||
mutationFn: project.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.projects.all });
|
||||
},
|
||||
});
|
||||
return useMutation({
|
||||
mutationKey: ["projects", "delete"],
|
||||
mutationFn: project.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.projects.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,45 +4,45 @@ import { queryKeys } from "@/lib/query/keys";
|
||||
import { sprint } from "@/lib/server";
|
||||
|
||||
export function useSprints(projectId?: number | null) {
|
||||
return useQuery<SprintRecord[]>({
|
||||
queryKey: queryKeys.sprints.byProject(projectId ?? 0),
|
||||
queryFn: () => sprint.byProject(projectId ?? 0),
|
||||
enabled: Boolean(projectId),
|
||||
});
|
||||
return useQuery<SprintRecord[]>({
|
||||
queryKey: queryKeys.sprints.byProject(projectId ?? 0),
|
||||
queryFn: () => sprint.byProject(projectId ?? 0),
|
||||
enabled: Boolean(projectId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateSprint() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<SprintRecord, Error, SprintCreateRequest>({
|
||||
mutationKey: ["sprints", "create"],
|
||||
mutationFn: sprint.create,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.byProject(variables.projectId) });
|
||||
},
|
||||
});
|
||||
return useMutation<SprintRecord, Error, SprintCreateRequest>({
|
||||
mutationKey: ["sprints", "create"],
|
||||
mutationFn: sprint.create,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.byProject(variables.projectId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateSprint() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<SprintRecord, Error, SprintUpdateRequest>({
|
||||
mutationKey: ["sprints", "update"],
|
||||
mutationFn: sprint.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.all });
|
||||
},
|
||||
});
|
||||
return useMutation<SprintRecord, Error, SprintUpdateRequest>({
|
||||
mutationKey: ["sprints", "update"],
|
||||
mutationFn: sprint.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSprint() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["sprints", "delete"],
|
||||
mutationFn: sprint.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.all });
|
||||
},
|
||||
});
|
||||
return useMutation({
|
||||
mutationKey: ["sprints", "delete"],
|
||||
mutationFn: sprint.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,47 +4,47 @@ import { queryKeys } from "@/lib/query/keys";
|
||||
import { timer } from "@/lib/server";
|
||||
|
||||
export function useTimerState(issueId?: number | null, options?: { refetchInterval?: number }) {
|
||||
return useQuery<TimerState>({
|
||||
queryKey: queryKeys.timers.active(issueId ?? 0),
|
||||
queryFn: () => timer.get(issueId ?? 0),
|
||||
enabled: Boolean(issueId),
|
||||
refetchInterval: options?.refetchInterval,
|
||||
refetchIntervalInBackground: false,
|
||||
});
|
||||
return useQuery<TimerState>({
|
||||
queryKey: queryKeys.timers.active(issueId ?? 0),
|
||||
queryFn: () => timer.get(issueId ?? 0),
|
||||
enabled: Boolean(issueId),
|
||||
refetchInterval: options?.refetchInterval,
|
||||
refetchIntervalInBackground: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useInactiveTimers(issueId?: number | null, options?: { refetchInterval?: number }) {
|
||||
return useQuery<TimerState[]>({
|
||||
queryKey: queryKeys.timers.inactive(issueId ?? 0),
|
||||
queryFn: () => timer.getInactive(issueId ?? 0),
|
||||
enabled: Boolean(issueId),
|
||||
refetchInterval: options?.refetchInterval,
|
||||
refetchIntervalInBackground: false,
|
||||
});
|
||||
return useQuery<TimerState[]>({
|
||||
queryKey: queryKeys.timers.inactive(issueId ?? 0),
|
||||
queryFn: () => timer.getInactive(issueId ?? 0),
|
||||
enabled: Boolean(issueId),
|
||||
refetchInterval: options?.refetchInterval,
|
||||
refetchIntervalInBackground: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useToggleTimer() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<TimerState, Error, TimerToggleRequest>({
|
||||
mutationKey: ["timers", "toggle"],
|
||||
mutationFn: timer.toggle,
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.setQueryData(queryKeys.timers.active(variables.issueId), data);
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.inactive(variables.issueId) });
|
||||
},
|
||||
});
|
||||
return useMutation<TimerState, Error, TimerToggleRequest>({
|
||||
mutationKey: ["timers", "toggle"],
|
||||
mutationFn: timer.toggle,
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.setQueryData(queryKeys.timers.active(variables.issueId), data);
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.inactive(variables.issueId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useEndTimer() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<TimerState, Error, TimerEndRequest>({
|
||||
mutationKey: ["timers", "end"],
|
||||
mutationFn: timer.end,
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.setQueryData(queryKeys.timers.active(variables.issueId), data);
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.inactive(variables.issueId) });
|
||||
},
|
||||
});
|
||||
return useMutation<TimerState, Error, TimerEndRequest>({
|
||||
mutationKey: ["timers", "end"],
|
||||
mutationFn: timer.end,
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.setQueryData(queryKeys.timers.active(variables.issueId), data);
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.inactive(variables.issueId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,33 +4,33 @@ import { queryKeys } from "@/lib/query/keys";
|
||||
import { user } from "@/lib/server";
|
||||
|
||||
export function useUserByUsername(username?: string | null) {
|
||||
return useQuery<UserRecord>({
|
||||
queryKey: queryKeys.users.byUsername(username ?? ""),
|
||||
queryFn: () => user.byUsername(username ?? ""),
|
||||
enabled: Boolean(username),
|
||||
});
|
||||
return useQuery<UserRecord>({
|
||||
queryKey: queryKeys.users.byUsername(username ?? ""),
|
||||
queryFn: () => user.byUsername(username ?? ""),
|
||||
enabled: Boolean(username),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateUser() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<UserRecord, Error, UserUpdateRequest>({
|
||||
mutationKey: ["users", "update"],
|
||||
mutationFn: user.update,
|
||||
onSuccess: (_data) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
||||
},
|
||||
});
|
||||
return useMutation<UserRecord, Error, UserUpdateRequest>({
|
||||
mutationKey: ["users", "update"],
|
||||
mutationFn: user.update,
|
||||
onSuccess: (_data) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUploadAvatar() {
|
||||
const queryClient = useQueryClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<string, Error, File>({
|
||||
mutationKey: ["users", "upload-avatar"],
|
||||
mutationFn: user.uploadAvatar,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
||||
},
|
||||
});
|
||||
return useMutation<string, Error, File>({
|
||||
mutationKey: ["users", "upload-avatar"],
|
||||
mutationFn: user.uploadAvatar,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
// query key factory for granular cache invalidation
|
||||
|
||||
export const queryKeys = {
|
||||
organisations: {
|
||||
all: ["organisations"] as const,
|
||||
byUser: () => [...queryKeys.organisations.all, "by-user"] as const,
|
||||
members: (orgId: number) => [...queryKeys.organisations.all, orgId, "members"] as const,
|
||||
},
|
||||
projects: {
|
||||
all: ["projects"] as const,
|
||||
byOrganisation: (orgId: number) => [...queryKeys.projects.all, "by-org", orgId] as const,
|
||||
},
|
||||
issues: {
|
||||
all: ["issues"] as const,
|
||||
byProject: (projectId: number) => [...queryKeys.issues.all, "by-project", projectId] as const,
|
||||
statusCount: (organisationId: number, status: string) =>
|
||||
[...queryKeys.issues.all, "status-count", organisationId, status] as const,
|
||||
},
|
||||
sprints: {
|
||||
all: ["sprints"] as const,
|
||||
byProject: (projectId: number) => [...queryKeys.sprints.all, "by-project", projectId] as const,
|
||||
},
|
||||
timers: {
|
||||
all: ["timers"] as const,
|
||||
active: (issueId: number) => [...queryKeys.timers.all, "active", issueId] as const,
|
||||
inactive: (issueId: number) => [...queryKeys.timers.all, "inactive", issueId] as const,
|
||||
list: (issueId: number) => [...queryKeys.timers.all, "list", issueId] as const,
|
||||
},
|
||||
users: {
|
||||
all: ["users"] as const,
|
||||
byUsername: (username: string) => [...queryKeys.users.all, "by-username", username] as const,
|
||||
},
|
||||
organisations: {
|
||||
all: ["organisations"] as const,
|
||||
byUser: () => [...queryKeys.organisations.all, "by-user"] as const,
|
||||
members: (orgId: number) => [...queryKeys.organisations.all, orgId, "members"] as const,
|
||||
},
|
||||
projects: {
|
||||
all: ["projects"] as const,
|
||||
byOrganisation: (orgId: number) => [...queryKeys.projects.all, "by-org", orgId] as const,
|
||||
},
|
||||
issues: {
|
||||
all: ["issues"] as const,
|
||||
byProject: (projectId: number) => [...queryKeys.issues.all, "by-project", projectId] as const,
|
||||
statusCount: (organisationId: number, status: string) =>
|
||||
[...queryKeys.issues.all, "status-count", organisationId, status] as const,
|
||||
},
|
||||
sprints: {
|
||||
all: ["sprints"] as const,
|
||||
byProject: (projectId: number) => [...queryKeys.sprints.all, "by-project", projectId] as const,
|
||||
},
|
||||
timers: {
|
||||
all: ["timers"] as const,
|
||||
active: (issueId: number) => [...queryKeys.timers.all, "active", issueId] as const,
|
||||
inactive: (issueId: number) => [...queryKeys.timers.all, "inactive", issueId] as const,
|
||||
list: (issueId: number) => [...queryKeys.timers.all, "list", issueId] as const,
|
||||
},
|
||||
users: {
|
||||
all: ["users"] as const,
|
||||
byUsername: (username: string) => [...queryKeys.users.all, "by-username", username] as const,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,28 +8,28 @@ export * as timer from "@/lib/server/timer";
|
||||
export * as user from "@/lib/server/user";
|
||||
|
||||
export async function getErrorMessage(res: Response, fallback: string): Promise<string> {
|
||||
const error = await res.json().catch(() => res.text());
|
||||
if (typeof error === "string") {
|
||||
return error || fallback;
|
||||
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<string, string[]>).flat();
|
||||
if (messages.length > 0) return messages.join(", ");
|
||||
}
|
||||
if (error && typeof error === "object") {
|
||||
if ("details" in error && error.details) {
|
||||
const messages = Object.values(error.details as Record<string, string[]>).flat();
|
||||
if (messages.length > 0) return messages.join(", ");
|
||||
}
|
||||
if ("error" in error && typeof error.error === "string") {
|
||||
return error.error || fallback;
|
||||
}
|
||||
if ("error" in error && typeof error.error === "string") {
|
||||
return error.error || fallback;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
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(", ");
|
||||
}
|
||||
return error.error;
|
||||
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(", ");
|
||||
}
|
||||
return error.error;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function byProject(projectId: number): Promise<IssueResponse[]> {
|
||||
const url = new URL(`${getServerURL()}/issues/by-project`);
|
||||
url.searchParams.set("projectId", `${projectId}`);
|
||||
const url = new URL(`${getServerURL()}/issues/by-project`);
|
||||
url.searchParams.set("projectId", `${projectId}`);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get issues by project (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get issues by project (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,26 +3,26 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function create(request: IssueCreateRequest): Promise<IssueRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/issue/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/issue/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 issue (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
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;
|
||||
const data = (await res.json()) as IssueRecord;
|
||||
if (!data.id) {
|
||||
throw new Error(`failed to create issue (${res.status})`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function remove(issueId: number): Promise<SuccessResponse> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/issue/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: issueId }),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/issue/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: issueId }),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete issue (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete issue (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function replaceStatus(request: IssuesReplaceStatusRequest): Promise<ReplaceStatusResponse> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/issues/replace-status`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/issues/replace-status`, {
|
||||
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 replace status (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to replace status (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function statusCount(organisationId: number, status: string): Promise<StatusCountResponse> {
|
||||
const url = new URL(`${getServerURL()}/issues/status-count`);
|
||||
url.searchParams.set("organisationId", `${organisationId}`);
|
||||
url.searchParams.set("status", status);
|
||||
const url = new URL(`${getServerURL()}/issues/status-count`);
|
||||
url.searchParams.set("organisationId", `${organisationId}`);
|
||||
url.searchParams.set("status", status);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get issue status count (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get issue status count (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function update(input: IssueUpdateRequest): Promise<IssueRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/issue/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/issue/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update issue (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update issue (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function addMember(request: OrgAddMemberRequest): Promise<OrganisationMemberRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/organisation/add-member`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisation/add-member`, {
|
||||
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 add member (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to add member (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function byUser(): Promise<OrganisationResponse[]> {
|
||||
const res = await fetch(`${getServerURL()}/organisations/by-user`, {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisations/by-user`, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get organisations (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get organisations (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,26 +3,26 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function create(request: OrgCreateRequest): Promise<OrganisationRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/organisation/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisation/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 organisation (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
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;
|
||||
const data = (await res.json()) as OrganisationRecord;
|
||||
if (!data.id) {
|
||||
throw new Error(`failed to create organisation (${res.status})`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function remove(organisationId: number): Promise<SuccessResponse> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/organisation/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: organisationId }),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisation/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: organisationId }),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete organisation (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete organisation (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function members(organisationId: number): Promise<OrganisationMemberResponse[]> {
|
||||
const url = new URL(`${getServerURL()}/organisation/members`);
|
||||
url.searchParams.set("organisationId", `${organisationId}`);
|
||||
const url = new URL(`${getServerURL()}/organisation/members`);
|
||||
url.searchParams.set("organisationId", `${organisationId}`);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get members (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get members (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function removeMember(request: OrgRemoveMemberRequest): Promise<SuccessResponse> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/organisation/remove-member`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisation/remove-member`, {
|
||||
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 remove member (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to remove member (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function update(input: OrgUpdateRequest): Promise<OrganisationRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/organisation/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisation/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update organisation (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update organisation (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,24 +3,24 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function updateMemberRole(
|
||||
request: OrgUpdateMemberRoleRequest,
|
||||
request: OrgUpdateMemberRoleRequest,
|
||||
): Promise<OrganisationMemberRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/organisation/update-member-role`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisation/update-member-role`, {
|
||||
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 update member role (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update member role (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -2,41 +2,41 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function uploadIcon(file: File, organisationId: number): Promise<string> {
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
const ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
const ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
||||
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
throw new Error("File size exceeds 5MB limit");
|
||||
}
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
throw new Error("File size exceeds 5MB limit");
|
||||
}
|
||||
|
||||
if (!ALLOWED_TYPES.includes(file.type)) {
|
||||
throw new Error("Invalid file type. Allowed types: png, jpg, jpeg, webp, gif");
|
||||
}
|
||||
if (!ALLOWED_TYPES.includes(file.type)) {
|
||||
throw new Error("Invalid file type. Allowed types: png, jpg, jpeg, webp, gif");
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("organisationId", organisationId.toString());
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("organisationId", organisationId.toString());
|
||||
|
||||
const csrfToken = getCsrfToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (csrfToken) headers["X-CSRF-Token"] = csrfToken;
|
||||
const csrfToken = getCsrfToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (csrfToken) headers["X-CSRF-Token"] = csrfToken;
|
||||
|
||||
const res = await fetch(`${getServerURL()}/organisation/upload-icon`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: formData,
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/organisation/upload-icon`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: formData,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `Failed to upload icon (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `Failed to upload icon (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (data.iconURL) {
|
||||
return data.iconURL;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (data.iconURL) {
|
||||
return data.iconURL;
|
||||
}
|
||||
|
||||
throw new Error("Failed to upload icon");
|
||||
throw new Error("Failed to upload icon");
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function byOrganisation(organisationId: number): Promise<ProjectResponse[]> {
|
||||
const url = new URL(`${getServerURL()}/projects/by-organisation`);
|
||||
url.searchParams.set("organisationId", `${organisationId}`);
|
||||
const url = new URL(`${getServerURL()}/projects/by-organisation`);
|
||||
url.searchParams.set("organisationId", `${organisationId}`);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get projects by organisation (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get projects by organisation (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,26 +3,26 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function create(request: ProjectCreateRequest): Promise<ProjectRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/project/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/project/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 project (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
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;
|
||||
const data = (await res.json()) as ProjectRecord;
|
||||
if (!data.id) {
|
||||
throw new Error(`failed to create project (${res.status})`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function remove(projectId: number): Promise<SuccessResponse> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/project/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: projectId }),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/project/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: projectId }),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete project (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete project (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function update(input: ProjectUpdateRequest): Promise<ProjectRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/project/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/project/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update project (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update project (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function byProject(projectId: number): Promise<SprintRecord[]> {
|
||||
const url = new URL(`${getServerURL()}/sprints/by-project`);
|
||||
url.searchParams.set("projectId", `${projectId}`);
|
||||
const url = new URL(`${getServerURL()}/sprints/by-project`);
|
||||
url.searchParams.set("projectId", `${projectId}`);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get sprints (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get sprints (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,29 +3,29 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function create(input: SprintCreateRequest): Promise<SprintRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/sprint/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...input,
|
||||
name: input.name.trim(),
|
||||
}),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/sprint/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...input,
|
||||
name: input.name.trim(),
|
||||
}),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to create sprint (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
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;
|
||||
const data = (await res.json()) as SprintRecord;
|
||||
if (!data.id) {
|
||||
throw new Error(`failed to create sprint (${res.status})`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function remove(sprintId: number): Promise<SuccessResponse> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/sprint/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: sprintId }),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/sprint/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({ id: sprintId }),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete sprint (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to delete sprint (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,25 +3,25 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function update(input: SprintUpdateRequest): Promise<SprintRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/sprint/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...input,
|
||||
name: input.name?.trim(),
|
||||
}),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/sprint/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...input,
|
||||
name: input.name?.trim(),
|
||||
}),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update sprint (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update sprint (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function end(request: TimerEndRequest): Promise<TimerState> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/timer/end`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/timer/end`, {
|
||||
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 end timer (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to end timer (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function get(issueId: number): Promise<TimerState> {
|
||||
const url = new URL(`${getServerURL()}/timer/get`);
|
||||
url.searchParams.set("issueId", `${issueId}`);
|
||||
const url = new URL(`${getServerURL()}/timer/get`);
|
||||
url.searchParams.set("issueId", `${issueId}`);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get timer (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get timer (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function getInactive(issueId: number): Promise<TimerState[]> {
|
||||
const url = new URL(`${getServerURL()}/timer/get-inactive`);
|
||||
url.searchParams.set("issueId", `${issueId}`);
|
||||
const url = new URL(`${getServerURL()}/timer/get-inactive`);
|
||||
url.searchParams.set("issueId", `${issueId}`);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get timers (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get timers (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = (await res.json()) as TimerState[];
|
||||
return data ?? [];
|
||||
const data = (await res.json()) as TimerState[];
|
||||
return data ?? [];
|
||||
}
|
||||
|
||||
@@ -2,28 +2,28 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
type TimerListInput = {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export async function list(input: TimerListInput = {}): Promise<unknown> {
|
||||
const url = new URL(`${getServerURL()}/timers`);
|
||||
if (input.limit != null) url.searchParams.set("limit", `${input.limit}`);
|
||||
if (input.offset != null) url.searchParams.set("offset", `${input.offset}`);
|
||||
const url = new URL(`${getServerURL()}/timers`);
|
||||
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 = {};
|
||||
if (csrfToken) headers["X-CSRF-Token"] = csrfToken;
|
||||
const csrfToken = getCsrfToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (csrfToken) headers["X-CSRF-Token"] = csrfToken;
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
headers,
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
headers,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get timers (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get timers (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function toggle(request: TimerToggleRequest): Promise<TimerState> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/timer/toggle`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/timer/toggle`, {
|
||||
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 toggle timer (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to toggle timer (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ import { getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function byUsername(username: string): Promise<UserRecord> {
|
||||
const url = new URL(`${getServerURL()}/user/by-username`);
|
||||
url.searchParams.set("username", username);
|
||||
const url = new URL(`${getServerURL()}/user/by-username`);
|
||||
url.searchParams.set("username", username);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(url.toString(), {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get user (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to get user (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -3,26 +3,26 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function update(request: UserUpdateRequest): Promise<UserRecord> {
|
||||
const csrfToken = getCsrfToken();
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const res = await fetch(`${getServerURL()}/user/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(csrfToken ? { "X-CSRF-Token": csrfToken } : {}),
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/user/update`, {
|
||||
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 update user (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `failed to update user (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = (await res.json()) as UserRecord;
|
||||
if (!data.id) {
|
||||
throw new Error(`failed to update user (${res.status})`);
|
||||
}
|
||||
return data;
|
||||
const data = (await res.json()) as UserRecord;
|
||||
if (!data.id) {
|
||||
throw new Error(`failed to update user (${res.status})`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -2,40 +2,40 @@ import { getCsrfToken, getServerURL } from "@/lib/utils";
|
||||
import { getErrorMessage } from "..";
|
||||
|
||||
export async function uploadAvatar(file: File): Promise<string> {
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
const ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
const ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
||||
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
throw new Error("File size exceeds 5MB limit");
|
||||
}
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
throw new Error("File size exceeds 5MB limit");
|
||||
}
|
||||
|
||||
if (!ALLOWED_TYPES.includes(file.type)) {
|
||||
throw new Error("Invalid file type. Allowed types: png, jpg, jpeg, webp, gif");
|
||||
}
|
||||
if (!ALLOWED_TYPES.includes(file.type)) {
|
||||
throw new Error("Invalid file type. Allowed types: png, jpg, jpeg, webp, gif");
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const csrfToken = getCsrfToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (csrfToken) headers["X-CSRF-Token"] = csrfToken;
|
||||
const csrfToken = getCsrfToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (csrfToken) headers["X-CSRF-Token"] = csrfToken;
|
||||
|
||||
const res = await fetch(`${getServerURL()}/user/upload-avatar`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: formData,
|
||||
credentials: "include",
|
||||
});
|
||||
const res = await fetch(`${getServerURL()}/user/upload-avatar`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: formData,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `Failed to upload avatar (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const message = await getErrorMessage(res, `Failed to upload avatar (${res.status})`);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (data.avatarURL) {
|
||||
return data.avatarURL;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (data.avatarURL) {
|
||||
return data.avatarURL;
|
||||
}
|
||||
|
||||
throw new Error("Failed to upload avatar");
|
||||
throw new Error("Failed to upload avatar");
|
||||
}
|
||||
|
||||
@@ -2,63 +2,63 @@ import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function issueID(key: string, num: number) {
|
||||
return `${key}-${num.toString().padStart(3, "0")}`;
|
||||
return `${key}-${num.toString().padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
export function getCsrfToken(): string | null {
|
||||
return sessionStorage.getItem("csrfToken");
|
||||
return sessionStorage.getItem("csrfToken");
|
||||
}
|
||||
|
||||
export function setCsrfToken(token: string): void {
|
||||
sessionStorage.setItem("csrfToken", token);
|
||||
sessionStorage.setItem("csrfToken", token);
|
||||
}
|
||||
|
||||
export function clearAuth(): void {
|
||||
sessionStorage.removeItem("csrfToken");
|
||||
localStorage.removeItem("user");
|
||||
localStorage.removeItem("selectedOrganisationId");
|
||||
localStorage.removeItem("selectedProjectId");
|
||||
sessionStorage.removeItem("csrfToken");
|
||||
localStorage.removeItem("user");
|
||||
localStorage.removeItem("selectedOrganisationId");
|
||||
localStorage.removeItem("selectedProjectId");
|
||||
}
|
||||
|
||||
export function capitalise(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
const ENV_SERVER_URL = import.meta.env.VITE_SERVER_URL?.trim();
|
||||
|
||||
export function getServerURL() {
|
||||
let serverURL =
|
||||
localStorage.getItem("serverURL") || // user-defined server URL
|
||||
ENV_SERVER_URL || // environment variable
|
||||
"https://tnirps.ob248.com"; // fallback
|
||||
if (serverURL.endsWith("/")) {
|
||||
serverURL = serverURL.slice(0, -1);
|
||||
}
|
||||
return serverURL;
|
||||
let serverURL =
|
||||
localStorage.getItem("serverURL") || // user-defined server URL
|
||||
ENV_SERVER_URL || // environment variable
|
||||
"https://tnirps.ob248.com"; // fallback
|
||||
if (serverURL.endsWith("/")) {
|
||||
serverURL = serverURL.slice(0, -1);
|
||||
}
|
||||
return serverURL;
|
||||
}
|
||||
|
||||
export function formatTime(ms: number): string {
|
||||
const totalSeconds = Math.floor(ms / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
const totalSeconds = Math.floor(ms / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export const DARK_TEXT_COLOUR = "#0a0a0a";
|
||||
const THRESHOLD = 0.6;
|
||||
|
||||
export const isLight = (hex: string): boolean => {
|
||||
const num = Number.parseInt(hex.replace("#", ""), 16);
|
||||
const r = (num >> 16) & 255;
|
||||
const g = (num >> 8) & 255;
|
||||
const b = num & 255;
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
return luminance > THRESHOLD;
|
||||
const num = Number.parseInt(hex.replace("#", ""), 16);
|
||||
const r = (num >> 16) & 255;
|
||||
const g = (num >> 8) & 255;
|
||||
const b = num & 255;
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
return luminance > THRESHOLD;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user