mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
added tanstack query keys and hooks
This commit is contained in:
16
packages/frontend/src/lib/query/client.ts
Normal file
16
packages/frontend/src/lib/query/client.ts
Normal file
@@ -0,0 +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,
|
||||
},
|
||||
},
|
||||
});
|
||||
35
packages/frontend/src/lib/query/hooks/derived.ts
Normal file
35
packages/frontend/src/lib/query/hooks/derived.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useMemo } from "react";
|
||||
import { useSelection } from "@/components/selection-provider";
|
||||
import { useIssues } from "@/lib/query/hooks/issues";
|
||||
import { useOrganisations } from "@/lib/query/hooks/organisations";
|
||||
import { useProjects } from "@/lib/query/hooks/projects";
|
||||
|
||||
export function useSelectedOrganisation() {
|
||||
const { selectedOrganisationId } = useSelection();
|
||||
const { data: organisations = [] } = useOrganisations();
|
||||
|
||||
return useMemo(
|
||||
() => organisations.find((org) => org.Organisation.id === selectedOrganisationId) ?? null,
|
||||
[organisations, selectedOrganisationId],
|
||||
);
|
||||
}
|
||||
|
||||
export function useSelectedProject() {
|
||||
const { selectedOrganisationId, selectedProjectId } = useSelection();
|
||||
const { data: projects = [] } = useProjects(selectedOrganisationId);
|
||||
|
||||
return useMemo(
|
||||
() => projects.find((project) => project.Project.id === selectedProjectId) ?? null,
|
||||
[projects, selectedProjectId],
|
||||
);
|
||||
}
|
||||
|
||||
export function useSelectedIssue() {
|
||||
const { selectedProjectId, selectedIssueId } = useSelection();
|
||||
const { data: issues = [] } = useIssues(selectedProjectId);
|
||||
|
||||
return useMemo(
|
||||
() => issues.find((issue) => issue.Issue.id === selectedIssueId) ?? null,
|
||||
[issues, selectedIssueId],
|
||||
);
|
||||
}
|
||||
7
packages/frontend/src/lib/query/hooks/index.ts
Normal file
7
packages/frontend/src/lib/query/hooks/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from "@/lib/query/hooks/derived";
|
||||
export * from "@/lib/query/hooks/issues";
|
||||
export * from "@/lib/query/hooks/organisations";
|
||||
export * from "@/lib/query/hooks/projects";
|
||||
export * from "@/lib/query/hooks/sprints";
|
||||
export * from "@/lib/query/hooks/timers";
|
||||
export * from "@/lib/query/hooks/users";
|
||||
78
packages/frontend/src/lib/query/hooks/issues.ts
Normal file
78
packages/frontend/src/lib/query/hooks/issues.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type {
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateIssue() {
|
||||
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),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateIssue() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<IssueRecord, Error, IssueUpdateRequest>({
|
||||
mutationKey: ["issues", "update"],
|
||||
mutationFn: issue.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteIssue() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<SuccessResponse, Error, number>({
|
||||
mutationKey: ["issues", "delete"],
|
||||
mutationFn: issue.delete,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useReplaceIssueStatus() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<unknown, Error, IssuesReplaceStatusRequest>({
|
||||
mutationKey: ["issues", "replace-status"],
|
||||
mutationFn: issue.replaceStatus,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.issues.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
101
packages/frontend/src/lib/query/hooks/organisations.ts
Normal file
101
packages/frontend/src/lib/query/hooks/organisations.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type {
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
export function useOrganisationMembers(organisationId?: number | null) {
|
||||
return useQuery<OrganisationMemberResponse[]>({
|
||||
queryKey: queryKeys.organisations.members(organisationId ?? 0),
|
||||
queryFn: () => organisation.members(organisationId ?? 0),
|
||||
enabled: Boolean(organisationId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateOrganisation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "create"],
|
||||
mutationFn: organisation.create,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateOrganisation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "update"],
|
||||
mutationFn: organisation.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteOrganisation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "delete"],
|
||||
mutationFn: organisation.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.organisations.byUser() });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAddOrganisationMember() {
|
||||
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),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveOrganisationMember() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
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();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["organisations", "members", "update-role"],
|
||||
mutationFn: organisation.updateMemberRole,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.organisations.members(variables.organisationId),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
55
packages/frontend/src/lib/query/hooks/projects.ts
Normal file
55
packages/frontend/src/lib/query/hooks/projects.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type {
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateProject() {
|
||||
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),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateProject() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ProjectRecord, Error, ProjectUpdateRequest>({
|
||||
mutationKey: ["projects", "update"],
|
||||
mutationFn: project.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.projects.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteProject() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["projects", "delete"],
|
||||
mutationFn: project.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.projects.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
48
packages/frontend/src/lib/query/hooks/sprints.ts
Normal file
48
packages/frontend/src/lib/query/hooks/sprints.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { SprintCreateRequest, SprintRecord, SprintUpdateRequest } from "@sprint/shared";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateSprint() {
|
||||
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) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateSprint() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<SprintRecord, Error, SprintUpdateRequest>({
|
||||
mutationKey: ["sprints", "update"],
|
||||
mutationFn: sprint.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSprint() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["sprints", "delete"],
|
||||
mutationFn: sprint.remove,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.sprints.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
50
packages/frontend/src/lib/query/hooks/timers.ts
Normal file
50
packages/frontend/src/lib/query/hooks/timers.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { TimerEndRequest, TimerState, TimerToggleRequest } from "@sprint/shared";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
export function useToggleTimer() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<TimerState, Error, TimerToggleRequest>({
|
||||
mutationKey: ["timers", "toggle"],
|
||||
mutationFn: timer.toggle,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.active(variables.issueId) });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.inactive(variables.issueId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useEndTimer() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<TimerState, Error, TimerEndRequest>({
|
||||
mutationKey: ["timers", "end"],
|
||||
mutationFn: timer.end,
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.active(variables.issueId) });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.timers.inactive(variables.issueId) });
|
||||
},
|
||||
});
|
||||
}
|
||||
36
packages/frontend/src/lib/query/hooks/users.ts
Normal file
36
packages/frontend/src/lib/query/hooks/users.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { UserRecord, UserUpdateRequest } from "@sprint/shared";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateUser() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
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();
|
||||
|
||||
return useMutation<string, Error, File>({
|
||||
mutationKey: ["users", "upload-avatar"],
|
||||
mutationFn: user.uploadAvatar,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
33
packages/frontend/src/lib/query/keys.ts
Normal file
33
packages/frontend/src/lib/query/keys.ts
Normal file
@@ -0,0 +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,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user