From 6edbe0b0d0debb5adb1736b89bd5fa6a05990cd6 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Thu, 13 Oct 2022 14:00:08 +0200 Subject: [PATCH 01/11] Add resolve issue feature --- features/issues/api/use-issues.tsx | 14 +- .../components/issue-list/issue-list.tsx | 141 +++++++++++++----- .../components/issue-list/issue-row.tsx | 34 ++++- 3 files changed, 146 insertions(+), 43 deletions(-) diff --git a/features/issues/api/use-issues.tsx b/features/issues/api/use-issues.tsx index fec9631..8d0899b 100644 --- a/features/issues/api/use-issues.tsx +++ b/features/issues/api/use-issues.tsx @@ -4,10 +4,12 @@ import axios from "axios"; import type { Page } from "@typings/page.types"; import type { Issue } from "../types/issue.types"; -async function getIssues(page: number) { - const { data } = await axios.get( - `https://prolog-api.profy.dev/issue?page=${page}` - ); +async function getIssues(page: number, options?: { signal?: AbortSignal }) { + const { data } = await axios.get("https://prolog-api.profy.dev/v2/issue", { + params: { page, status: "open" }, + signal: options?.signal, + headers: { Authorization: "my-access-token" }, + }); return data; } @@ -18,7 +20,7 @@ const commonQueryOptions = { export function useIssues(page: number) { const query = useQuery, Error>( ["issues", page], - () => getIssues(page), + ({ signal }) => getIssues(page, { signal }), { ...commonQueryOptions, staleTime: 60000 } ); @@ -28,7 +30,7 @@ export function useIssues(page: number) { if (query.data?.meta.hasNextPage) { queryClient.prefetchQuery( ["issues", page + 1], - () => getIssues(page + 1), + async ({ signal }) => getIssues(page + 1, { signal }), commonQueryOptions ); } diff --git a/features/issues/components/issue-list/issue-list.tsx b/features/issues/components/issue-list/issue-list.tsx index d576363..94e9713 100644 --- a/features/issues/components/issue-list/issue-list.tsx +++ b/features/issues/components/issue-list/issue-list.tsx @@ -1,8 +1,12 @@ +import { useRef, useState } from "react"; import { useRouter } from "next/router"; import styled from "styled-components"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import axios from "axios"; import { useIssues } from "@features/issues"; -import { ProjectLanguage, useProjects } from "@features/projects"; +import { ProjectLanguage } from "@features/projects"; import { color, space, textFont } from "@styles/theme"; +import { Issue } from "@features/issues"; import { IssueRow } from "./issue-row"; const Container = styled.div` @@ -62,39 +66,102 @@ const PageNumber = styled.span` `; export function IssueList() { - const router = useRouter(); - const page = Number(router.query.page || 1); - const navigateToPage = (newPage: number) => - router.push({ - pathname: router.pathname, - query: { page: newPage }, - }); - - const issuesPage = useIssues(page); - const projects = useProjects(); - - if (projects.isLoading || issuesPage.isLoading) { - return
Loading
; - } - - if (projects.isError) { - console.error(projects.error); - return
Error loading projects: {projects.error.message}
; - } - - if (issuesPage.isError) { - console.error(issuesPage.error); - return
Error loading issues: {issuesPage.error.message}
; - } - - const projectIdToLanguage = (projects.data || []).reduce( - (prev, project) => ({ - ...prev, - [project.id]: project.language, - }), - {} as Record + const [page, setPage] = useState(1); + + /********* THE "SIMPLE" APPROACH *********/ + // const [data, setData] = useState({ items: [], meta: undefined }); + // const [invalidated, setInvalidated] = useState(0); + // useEffect(() => { + // axios + // .get("https://prolog-api.profy.dev/v2/issue?status=open", { + // headers: { Authorization: "my-access-token" }, + // }) + // .then(({ data }) => setData(data)); + // }, [invalidated]); + // const { items, meta } = data; + + // const onClickResolve = (issueId) => { + // // optimistic update: remove issue from the list + // setData((issues) => issues.filter((issue) => issue.id !== issueId)); + + // axios + // .patch( + // `https://prolog-api.profy.dev/v2/issue/${issueId}`, + // { status: "resolved" }, + // { headers: { Authorization: "my-access-token" } } + // ) + // .then(() => { + // setInvalidated((count) => count + 1); + // }); + // }; + + /********* THE EFFICIENT APPROACH *********/ + const issuePage = useIssues(page); + + const queryClient = useQueryClient(); + const ongoingMutationCount = useRef(0); + const resolveIssueMutation = useMutation( + (issueId) => + axios.patch( + `https://prolog-api.profy.dev/v2/issue/${issueId}`, + { status: "resolved" }, + { headers: { Authorization: "my-access-token" } } + ), + { + onMutate: async (issueId: string) => { + ongoingMutationCount.current += 1; + + await queryClient.cancelQueries(["issues"]); + + const currentPage = queryClient.getQueryData<{ items: Issue[] }>([ + "issues", + page, + ]); + const nextPage = queryClient.getQueryData<{ items: Issue[] }>([ + "issues", + page + 1, + ]); + + if (!currentPage) { + return; + } + + const newItems = currentPage.items.filter(({ id }) => id !== issueId); + + if (nextPage?.items.length) { + const lastIssueOnPage = + currentPage.items[currentPage.items.length - 1]; + const indexOnNextPage = nextPage.items.findIndex( + (issue) => issue.id === lastIssueOnPage.id + ); + const nextIssue = nextPage.items[indexOnNextPage + 1]; + if (nextIssue) { + newItems.push(nextIssue); + } + } + + queryClient.setQueryData(["issues", page], { + ...currentPage, + items: newItems, + }); + + return { currentIssuesPage: currentPage }; + }, + onError: (err, issueId, context) => { + if (context?.currentIssuesPage) { + queryClient.setQueryData(["issues", page], context.currentIssuesPage); + } + }, + onSettled: () => { + ongoingMutationCount.current -= 1; + if (ongoingMutationCount.current === 0) { + queryClient.invalidateQueries(["issues"]); + } + }, + } ); - const { items, meta } = issuesPage.data || {}; + + const { items, meta } = issuePage.data || {}; return ( @@ -112,7 +179,9 @@ export function IssueList() { onClickResolve(issue.id)} + resolveIssue={() => resolveIssueMutation.mutate(issue.id)} /> ))} @@ -120,13 +189,13 @@ export function IssueList() {
navigateToPage(page - 1)} + onClick={() => setPage(page - 1)} disabled={page === 1} > Previous navigateToPage(page + 1)} + onClick={() => setPage(page + 1)} disabled={page === meta?.totalPages} > Next diff --git a/features/issues/components/issue-list/issue-row.tsx b/features/issues/components/issue-list/issue-row.tsx index ce5da00..c8ffad6 100644 --- a/features/issues/components/issue-list/issue-row.tsx +++ b/features/issues/components/issue-list/issue-row.tsx @@ -9,6 +9,7 @@ import type { Issue } from "../../types/issue.types"; type IssueRowProps = { projectLanguage: ProjectLanguage; issue: Issue; + resolveIssue: () => void; }; const levelColors = { @@ -47,9 +48,30 @@ const ErrorType = styled.span` ${textFont("sm", "medium")} `; -export function IssueRow({ projectLanguage, issue }: IssueRowProps) { +const ResolveButton = styled.button` + width: 1.5rem; + height: 1.5rem; + font-size: 0.8rem; + display: flex; + align-items: center; + justify-content: center; + color: #aaa; + border: 1px solid #aaa; + background: none; + border-radius: 50%; + padding: none; + margin: none; + cursor: pointer; +`; + +export function IssueRow({ + projectLanguage, + issue, + resolveIssue, +}: IssueRowProps) { const { name, message, stack, level, numEvents } = issue; const firstLineOfStackTrace = stack.split("\n")[1]; + return ( @@ -72,6 +94,16 @@ export function IssueRow({ projectLanguage, issue }: IssueRowProps) { {numEvents} {numEvents} + + + + + + + ); } From 9a0e4867d5365b4ace5066511c8a0f1b391bc549 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 10:35:31 +0200 Subject: [PATCH 02/11] Fix useQuery option --- features/issues/api/use-issues.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/issues/api/use-issues.tsx b/features/issues/api/use-issues.tsx index 8d0899b..4a214ab 100644 --- a/features/issues/api/use-issues.tsx +++ b/features/issues/api/use-issues.tsx @@ -21,7 +21,7 @@ export function useIssues(page: number) { const query = useQuery, Error>( ["issues", page], ({ signal }) => getIssues(page, { signal }), - { ...commonQueryOptions, staleTime: 60000 } + { ...commonQueryOptions, keepPreviousData: true } ); // Prefetch the next page! From f28a31d992cbce67fe8f2d75ad485b333d931520 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 10:46:06 +0200 Subject: [PATCH 03/11] Preparing tutorial starter --- features/issues/api/use-issues.tsx | 39 ---------- .../components/issue-list/issue-list.tsx | 77 ++++++++++--------- features/issues/index.ts | 1 - 3 files changed, 41 insertions(+), 76 deletions(-) delete mode 100644 features/issues/api/use-issues.tsx diff --git a/features/issues/api/use-issues.tsx b/features/issues/api/use-issues.tsx deleted file mode 100644 index 4a214ab..0000000 --- a/features/issues/api/use-issues.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useEffect } from "react"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import axios from "axios"; -import type { Page } from "@typings/page.types"; -import type { Issue } from "../types/issue.types"; - -async function getIssues(page: number, options?: { signal?: AbortSignal }) { - const { data } = await axios.get("https://prolog-api.profy.dev/v2/issue", { - params: { page, status: "open" }, - signal: options?.signal, - headers: { Authorization: "my-access-token" }, - }); - return data; -} - -const commonQueryOptions = { - staleTime: 60000, -}; - -export function useIssues(page: number) { - const query = useQuery, Error>( - ["issues", page], - ({ signal }) => getIssues(page, { signal }), - { ...commonQueryOptions, keepPreviousData: true } - ); - - // Prefetch the next page! - const queryClient = useQueryClient(); - useEffect(() => { - if (query.data?.meta.hasNextPage) { - queryClient.prefetchQuery( - ["issues", page + 1], - async ({ signal }) => getIssues(page + 1, { signal }), - commonQueryOptions - ); - } - }, [query.data, page, queryClient]); - return query; -} diff --git a/features/issues/components/issue-list/issue-list.tsx b/features/issues/components/issue-list/issue-list.tsx index 94e9713..eee9d91 100644 --- a/features/issues/components/issue-list/issue-list.tsx +++ b/features/issues/components/issue-list/issue-list.tsx @@ -1,13 +1,12 @@ -import { useRef, useState } from "react"; -import { useRouter } from "next/router"; +import { useEffect, useRef, useState } from "react"; import styled from "styled-components"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; -import { useIssues } from "@features/issues"; import { ProjectLanguage } from "@features/projects"; import { color, space, textFont } from "@styles/theme"; import { Issue } from "@features/issues"; import { IssueRow } from "./issue-row"; +import { Page } from "@typings/page.types"; const Container = styled.div` background: white; @@ -68,37 +67,46 @@ const PageNumber = styled.span` export function IssueList() { const [page, setPage] = useState(1); - /********* THE "SIMPLE" APPROACH *********/ - // const [data, setData] = useState({ items: [], meta: undefined }); - // const [invalidated, setInvalidated] = useState(0); - // useEffect(() => { - // axios - // .get("https://prolog-api.profy.dev/v2/issue?status=open", { - // headers: { Authorization: "my-access-token" }, - // }) - // .then(({ data }) => setData(data)); - // }, [invalidated]); - // const { items, meta } = data; - - // const onClickResolve = (issueId) => { - // // optimistic update: remove issue from the list - // setData((issues) => issues.filter((issue) => issue.id !== issueId)); - - // axios - // .patch( - // `https://prolog-api.profy.dev/v2/issue/${issueId}`, - // { status: "resolved" }, - // { headers: { Authorization: "my-access-token" } } - // ) - // .then(() => { - // setInvalidated((count) => count + 1); - // }); - // }; - - /********* THE EFFICIENT APPROACH *********/ - const issuePage = useIssues(page); + const issuePage = useQuery, Error>( + ["issues", page], + async ({ signal }) => { + const { data } = await axios.get( + "https://prolog-api.profy.dev/v2/issue", + { + params: { page, status: "open" }, + signal, + headers: { Authorization: "my-access-token" }, + } + ); + return data; + }, + { staleTime: 60000, keepPreviousData: true } + ); + // Prefetch the next page! const queryClient = useQueryClient(); + useEffect(() => { + if (issuePage.data?.meta.hasNextPage) { + queryClient.prefetchQuery( + ["issues", page + 1], + async ({ signal }) => { + const { data } = await axios.get( + "https://prolog-api.profy.dev/v2/issue", + { + params: { page, status: "open" }, + signal, + headers: { Authorization: "my-access-token" }, + } + ); + return data; + }, + { staleTime: 60000 } + ); + } + }, [issuePage.data, page, queryClient]); + + const { items, meta } = issuePage.data || {}; + const ongoingMutationCount = useRef(0); const resolveIssueMutation = useMutation( (issueId) => @@ -161,8 +169,6 @@ export function IssueList() { } ); - const { items, meta } = issuePage.data || {}; - return ( @@ -180,7 +186,6 @@ export function IssueList() { key={issue.id} issue={issue} projectLanguage={ProjectLanguage.react} - // resolveIssue={() => onClickResolve(issue.id)} resolveIssue={() => resolveIssueMutation.mutate(issue.id)} /> ))} diff --git a/features/issues/index.ts b/features/issues/index.ts index aefa94b..51719d3 100644 --- a/features/issues/index.ts +++ b/features/issues/index.ts @@ -1,3 +1,2 @@ -export * from "./api/use-issues"; export * from "./components/issue-list"; export * from "./types/issue.types"; From 9c13352ab15750df83b5b91c4e1a21a85b2b3979 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 10:59:07 +0200 Subject: [PATCH 04/11] Extract query hooks --- features/issues/api/index.ts | 2 + features/issues/api/use-get-issues.tsx | 46 ++++++++ features/issues/api/use-resolve-issue.tsx | 69 +++++++++++ .../components/issue-list/issue-list.tsx | 110 +----------------- features/issues/index.ts | 1 + features/projects/api/index.ts | 1 + features/projects/index.ts | 2 +- 7 files changed, 125 insertions(+), 106 deletions(-) create mode 100644 features/issues/api/index.ts create mode 100644 features/issues/api/use-get-issues.tsx create mode 100644 features/issues/api/use-resolve-issue.tsx create mode 100644 features/projects/api/index.ts diff --git a/features/issues/api/index.ts b/features/issues/api/index.ts new file mode 100644 index 0000000..7122906 --- /dev/null +++ b/features/issues/api/index.ts @@ -0,0 +1,2 @@ +export * from "./use-get-issues"; +export * from "./use-resolve-issue"; diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx new file mode 100644 index 0000000..d416a1d --- /dev/null +++ b/features/issues/api/use-get-issues.tsx @@ -0,0 +1,46 @@ +import { useEffect } from "react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import axios from "axios"; +import type { Page } from "@typings/page.types"; +import type { Issue } from "@features/issues"; + +export function useGetIssues(page: number) { + const query = useQuery, Error>( + ["issues", page], + async ({ signal }) => { + const { data } = await axios.get( + "https://prolog-api.profy.dev/v2/issue", + { + params: { page, status: "open" }, + signal, + headers: { Authorization: "my-access-token" }, + } + ); + return data; + }, + { staleTime: 60000, keepPreviousData: true } + ); + + // Prefetch the next page! + const queryClient = useQueryClient(); + useEffect(() => { + if (query.data?.meta.hasNextPage) { + queryClient.prefetchQuery( + ["issues", page + 1], + async ({ signal }) => { + const { data } = await axios.get( + "https://prolog-api.profy.dev/v2/issue", + { + params: { page: page + 1, status: "open" }, + signal, + headers: { Authorization: "my-access-token" }, + } + ); + return data; + }, + { staleTime: 60000 } + ); + } + }, [query.data, page, queryClient]); + return query; +} diff --git a/features/issues/api/use-resolve-issue.tsx b/features/issues/api/use-resolve-issue.tsx new file mode 100644 index 0000000..aa6678a --- /dev/null +++ b/features/issues/api/use-resolve-issue.tsx @@ -0,0 +1,69 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import axios from "axios"; +import { useRef } from "react"; +import { Issue } from "@features/issues"; + +export function useResolveIssue(page: number) { + const queryClient = useQueryClient(); + const ongoingMutationCount = useRef(0); + return useMutation( + (issueId) => + axios.patch( + `https://prolog-api.profy.dev/v2/issue/${issueId}`, + { status: "resolved" }, + { headers: { Authorization: "my-access-token" } } + ), + { + onMutate: async (issueId: string) => { + ongoingMutationCount.current += 1; + + await queryClient.cancelQueries(["issues"]); + + const currentPage = queryClient.getQueryData<{ items: Issue[] }>([ + "issues", + page, + ]); + const nextPage = queryClient.getQueryData<{ items: Issue[] }>([ + "issues", + page + 1, + ]); + + if (!currentPage) { + return; + } + + const newItems = currentPage.items.filter(({ id }) => id !== issueId); + + if (nextPage?.items.length) { + const lastIssueOnPage = + currentPage.items[currentPage.items.length - 1]; + const indexOnNextPage = nextPage.items.findIndex( + (issue) => issue.id === lastIssueOnPage.id + ); + const nextIssue = nextPage.items[indexOnNextPage + 1]; + if (nextIssue) { + newItems.push(nextIssue); + } + } + + queryClient.setQueryData(["issues", page], { + ...currentPage, + items: newItems, + }); + + return { currentIssuesPage: currentPage }; + }, + onError: (err, issueId, context) => { + if (context?.currentIssuesPage) { + queryClient.setQueryData(["issues", page], context.currentIssuesPage); + } + }, + onSettled: () => { + ongoingMutationCount.current -= 1; + if (ongoingMutationCount.current === 0) { + queryClient.invalidateQueries(["issues"]); + } + }, + } + ); +} diff --git a/features/issues/components/issue-list/issue-list.tsx b/features/issues/components/issue-list/issue-list.tsx index eee9d91..20d7f04 100644 --- a/features/issues/components/issue-list/issue-list.tsx +++ b/features/issues/components/issue-list/issue-list.tsx @@ -1,12 +1,9 @@ -import { useEffect, useRef, useState } from "react"; +import { useState } from "react"; import styled from "styled-components"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import axios from "axios"; import { ProjectLanguage } from "@features/projects"; import { color, space, textFont } from "@styles/theme"; -import { Issue } from "@features/issues"; import { IssueRow } from "./issue-row"; -import { Page } from "@typings/page.types"; +import { useGetIssues, useResolveIssue } from "../../api"; const Container = styled.div` background: white; @@ -67,108 +64,11 @@ const PageNumber = styled.span` export function IssueList() { const [page, setPage] = useState(1); - const issuePage = useQuery, Error>( - ["issues", page], - async ({ signal }) => { - const { data } = await axios.get( - "https://prolog-api.profy.dev/v2/issue", - { - params: { page, status: "open" }, - signal, - headers: { Authorization: "my-access-token" }, - } - ); - return data; - }, - { staleTime: 60000, keepPreviousData: true } - ); - - // Prefetch the next page! - const queryClient = useQueryClient(); - useEffect(() => { - if (issuePage.data?.meta.hasNextPage) { - queryClient.prefetchQuery( - ["issues", page + 1], - async ({ signal }) => { - const { data } = await axios.get( - "https://prolog-api.profy.dev/v2/issue", - { - params: { page, status: "open" }, - signal, - headers: { Authorization: "my-access-token" }, - } - ); - return data; - }, - { staleTime: 60000 } - ); - } - }, [issuePage.data, page, queryClient]); + const issuePage = useGetIssues(page); + const resolveIssue = useResolveIssue(page); const { items, meta } = issuePage.data || {}; - const ongoingMutationCount = useRef(0); - const resolveIssueMutation = useMutation( - (issueId) => - axios.patch( - `https://prolog-api.profy.dev/v2/issue/${issueId}`, - { status: "resolved" }, - { headers: { Authorization: "my-access-token" } } - ), - { - onMutate: async (issueId: string) => { - ongoingMutationCount.current += 1; - - await queryClient.cancelQueries(["issues"]); - - const currentPage = queryClient.getQueryData<{ items: Issue[] }>([ - "issues", - page, - ]); - const nextPage = queryClient.getQueryData<{ items: Issue[] }>([ - "issues", - page + 1, - ]); - - if (!currentPage) { - return; - } - - const newItems = currentPage.items.filter(({ id }) => id !== issueId); - - if (nextPage?.items.length) { - const lastIssueOnPage = - currentPage.items[currentPage.items.length - 1]; - const indexOnNextPage = nextPage.items.findIndex( - (issue) => issue.id === lastIssueOnPage.id - ); - const nextIssue = nextPage.items[indexOnNextPage + 1]; - if (nextIssue) { - newItems.push(nextIssue); - } - } - - queryClient.setQueryData(["issues", page], { - ...currentPage, - items: newItems, - }); - - return { currentIssuesPage: currentPage }; - }, - onError: (err, issueId, context) => { - if (context?.currentIssuesPage) { - queryClient.setQueryData(["issues", page], context.currentIssuesPage); - } - }, - onSettled: () => { - ongoingMutationCount.current -= 1; - if (ongoingMutationCount.current === 0) { - queryClient.invalidateQueries(["issues"]); - } - }, - } - ); - return (
@@ -186,7 +86,7 @@ export function IssueList() { key={issue.id} issue={issue} projectLanguage={ProjectLanguage.react} - resolveIssue={() => resolveIssueMutation.mutate(issue.id)} + resolveIssue={() => resolveIssue.mutate(issue.id)} /> ))} diff --git a/features/issues/index.ts b/features/issues/index.ts index 51719d3..abe4bcf 100644 --- a/features/issues/index.ts +++ b/features/issues/index.ts @@ -1,2 +1,3 @@ +export * from "./api"; export * from "./components/issue-list"; export * from "./types/issue.types"; diff --git a/features/projects/api/index.ts b/features/projects/api/index.ts new file mode 100644 index 0000000..4d090e6 --- /dev/null +++ b/features/projects/api/index.ts @@ -0,0 +1 @@ +export * from "./use-projects"; diff --git a/features/projects/index.ts b/features/projects/index.ts index 654d652..0e6a47c 100644 --- a/features/projects/index.ts +++ b/features/projects/index.ts @@ -1,3 +1,3 @@ -export * from "./api/use-projects"; +export * from "./api"; export * from "./components/project-list"; export * from "./types/project.types"; From 5d2c2b7b2cf2dc8a99872554620497aab7de7e97 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 11:11:49 +0200 Subject: [PATCH 05/11] Reuse common code --- features/issues/api/use-get-issues.tsx | 46 +++++----- features/issues/api/use-resolve-issue.tsx | 106 +++++++++++----------- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx index d416a1d..71755f5 100644 --- a/features/issues/api/use-get-issues.tsx +++ b/features/issues/api/use-get-issues.tsx @@ -4,20 +4,28 @@ import axios from "axios"; import type { Page } from "@typings/page.types"; import type { Issue } from "@features/issues"; +const QUERY_KEY = "issues"; + +export function getQueryKey(page?: number) { + if (page === undefined) { + return [QUERY_KEY]; + } + return [QUERY_KEY, page]; +} + +async function getIssues(page: number, options?: { signal?: AbortSignal }) { + const { data } = await axios.get("https://prolog-api.profy.dev/v2/issue", { + params: { page, status: "open" }, + signal: options?.signal, + headers: { Authorization: "my-access-token" }, + }); + return data; +} + export function useGetIssues(page: number) { const query = useQuery, Error>( - ["issues", page], - async ({ signal }) => { - const { data } = await axios.get( - "https://prolog-api.profy.dev/v2/issue", - { - params: { page, status: "open" }, - signal, - headers: { Authorization: "my-access-token" }, - } - ); - return data; - }, + getQueryKey(page), + ({ signal }) => getIssues(page, { signal }), { staleTime: 60000, keepPreviousData: true } ); @@ -26,18 +34,8 @@ export function useGetIssues(page: number) { useEffect(() => { if (query.data?.meta.hasNextPage) { queryClient.prefetchQuery( - ["issues", page + 1], - async ({ signal }) => { - const { data } = await axios.get( - "https://prolog-api.profy.dev/v2/issue", - { - params: { page: page + 1, status: "open" }, - signal, - headers: { Authorization: "my-access-token" }, - } - ); - return data; - }, + getQueryKey(page + 1), + ({ signal }) => getIssues(page + 1, { signal }), { staleTime: 60000 } ); } diff --git a/features/issues/api/use-resolve-issue.tsx b/features/issues/api/use-resolve-issue.tsx index aa6678a..05e9cca 100644 --- a/features/issues/api/use-resolve-issue.tsx +++ b/features/issues/api/use-resolve-issue.tsx @@ -2,68 +2,70 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; import { useRef } from "react"; import { Issue } from "@features/issues"; +import * as GetIssues from "./use-get-issues"; + +async function resolveIssue(issueId: string) { + const { data } = await axios.patch( + `https://prolog-api.profy.dev/v2/issue/${issueId}`, + { status: "resolved" }, + { headers: { Authorization: "my-access-token" } } + ); + return data; +} export function useResolveIssue(page: number) { const queryClient = useQueryClient(); const ongoingMutationCount = useRef(0); - return useMutation( - (issueId) => - axios.patch( - `https://prolog-api.profy.dev/v2/issue/${issueId}`, - { status: "resolved" }, - { headers: { Authorization: "my-access-token" } } - ), - { - onMutate: async (issueId: string) => { - ongoingMutationCount.current += 1; + return useMutation((issueId) => resolveIssue(issueId), { + onMutate: async (issueId: string) => { + ongoingMutationCount.current += 1; - await queryClient.cancelQueries(["issues"]); + await queryClient.cancelQueries(GetIssues.getQueryKey()); - const currentPage = queryClient.getQueryData<{ items: Issue[] }>([ - "issues", - page, - ]); - const nextPage = queryClient.getQueryData<{ items: Issue[] }>([ - "issues", - page + 1, - ]); + const currentPage = queryClient.getQueryData<{ items: Issue[] }>( + GetIssues.getQueryKey(page) + ); + const nextPage = queryClient.getQueryData<{ items: Issue[] }>( + GetIssues.getQueryKey(page + 1) + ); - if (!currentPage) { - return; - } + if (!currentPage) { + return; + } - const newItems = currentPage.items.filter(({ id }) => id !== issueId); + const newItems = currentPage.items.filter(({ id }) => id !== issueId); - if (nextPage?.items.length) { - const lastIssueOnPage = - currentPage.items[currentPage.items.length - 1]; - const indexOnNextPage = nextPage.items.findIndex( - (issue) => issue.id === lastIssueOnPage.id - ); - const nextIssue = nextPage.items[indexOnNextPage + 1]; - if (nextIssue) { - newItems.push(nextIssue); - } + if (nextPage?.items.length) { + const lastIssueOnPage = currentPage.items[currentPage.items.length - 1]; + const indexOnNextPage = nextPage.items.findIndex( + (issue) => issue.id === lastIssueOnPage.id + ); + const nextIssue = nextPage.items[indexOnNextPage + 1]; + if (nextIssue) { + newItems.push(nextIssue); } + } - queryClient.setQueryData(["issues", page], { - ...currentPage, - items: newItems, - }); + queryClient.setQueryData(GetIssues.getQueryKey(page), { + ...currentPage, + items: newItems, + }); - return { currentIssuesPage: currentPage }; - }, - onError: (err, issueId, context) => { - if (context?.currentIssuesPage) { - queryClient.setQueryData(["issues", page], context.currentIssuesPage); - } - }, - onSettled: () => { - ongoingMutationCount.current -= 1; - if (ongoingMutationCount.current === 0) { - queryClient.invalidateQueries(["issues"]); - } - }, - } - ); + return { currentIssuesPage: currentPage }; + }, + onError: (err, issueId, context) => { + if (context?.currentIssuesPage) { + queryClient.setQueryData( + GetIssues.getQueryKey(page), + context.currentIssuesPage + ); + } + }, + onSettled: () => { + ongoingMutationCount.current -= 1; + if (ongoingMutationCount.current === 0) { + queryClient.invalidateQueries(GetIssues.getQueryKey()); + } + }, + }); } From 4a9212092c6417d8ecd2b95ba2f0fb2b2c6ce90c Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 11:39:07 +0200 Subject: [PATCH 06/11] Use global axios instance --- .env.template | 1 + .gitignore | 1 + api/axios.ts | 11 +++++++++++ features/issues/api/use-get-issues.tsx | 4 ++-- features/issues/api/use-resolve-issue.tsx | 6 +++--- features/projects/api/use-projects.tsx | 6 +++--- tsconfig.json | 3 ++- 7 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 .env.template create mode 100644 api/axios.ts diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..6f3bbc7 --- /dev/null +++ b/.env.template @@ -0,0 +1 @@ +NEXT_PUBLIC_API_BASE_URL=https://prolog-api.profy.dev/v2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5e105e9..fa97ed2 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ yarn-error.log* .vscode .eslintcache +.env # Cypress test videos cypress/videos/ diff --git a/api/axios.ts b/api/axios.ts new file mode 100644 index 0000000..cfc0705 --- /dev/null +++ b/api/axios.ts @@ -0,0 +1,11 @@ +import assert from "assert"; +import Axios from "axios"; + +assert( + process.env.NEXT_PUBLIC_API_BASE_URL, + "env variable not set: NEXT_PUBLIC_API_BASE_URL" +); + +export const axios = Axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, +}); diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx index 71755f5..f343d42 100644 --- a/features/issues/api/use-get-issues.tsx +++ b/features/issues/api/use-get-issues.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import axios from "axios"; +import { axios } from "@api/axios"; import type { Page } from "@typings/page.types"; import type { Issue } from "@features/issues"; @@ -14,7 +14,7 @@ export function getQueryKey(page?: number) { } async function getIssues(page: number, options?: { signal?: AbortSignal }) { - const { data } = await axios.get("https://prolog-api.profy.dev/v2/issue", { + const { data } = await axios.get("/issue", { params: { page, status: "open" }, signal: options?.signal, headers: { Authorization: "my-access-token" }, diff --git a/features/issues/api/use-resolve-issue.tsx b/features/issues/api/use-resolve-issue.tsx index 05e9cca..0f357ff 100644 --- a/features/issues/api/use-resolve-issue.tsx +++ b/features/issues/api/use-resolve-issue.tsx @@ -1,12 +1,12 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import axios from "axios"; import { useRef } from "react"; -import { Issue } from "@features/issues"; +import { axios } from "@api/axios"; import * as GetIssues from "./use-get-issues"; +import type { Issue } from "@features/issues"; async function resolveIssue(issueId: string) { const { data } = await axios.patch( - `https://prolog-api.profy.dev/v2/issue/${issueId}`, + `/issue/${issueId}`, { status: "resolved" }, { headers: { Authorization: "my-access-token" } } ); diff --git a/features/projects/api/use-projects.tsx b/features/projects/api/use-projects.tsx index d8182dd..d224bbc 100644 --- a/features/projects/api/use-projects.tsx +++ b/features/projects/api/use-projects.tsx @@ -1,9 +1,9 @@ import { useQuery } from "@tanstack/react-query"; -import axios from "axios"; -import { Project } from "../types/project.types"; +import { axios } from "@api/axios"; +import type { Project } from "@features/projects"; async function getProjects() { - const { data } = await axios.get("https://prolog-api.profy.dev/project"); + const { data } = await axios.get("/project"); return data; } diff --git a/tsconfig.json b/tsconfig.json index 4ed3e82..c35d661 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,9 +16,10 @@ "types": ["jest"], "baseUrl": ".", "paths": { - "@styles/*": ["styles/*"], + "@api/*": ["api/*"], "@config/*": ["config/*"], "@features/*": ["features/*"], + "@styles/*": ["styles/*"], "@typings/*": ["typings/*"] }, "noEmit": true From 6845c86aac726f7c31c5680d8f72a0d0b8d4354e Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 11:59:56 +0200 Subject: [PATCH 07/11] Set common headers in global axios instance --- .env.template | 3 ++- api/axios.ts | 20 +++++++++++++++++++- features/issues/api/use-get-issues.tsx | 1 - features/issues/api/use-resolve-issue.tsx | 8 +++----- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.env.template b/.env.template index 6f3bbc7..69f8728 100644 --- a/.env.template +++ b/.env.template @@ -1 +1,2 @@ -NEXT_PUBLIC_API_BASE_URL=https://prolog-api.profy.dev/v2 \ No newline at end of file +NEXT_PUBLIC_API_BASE_URL=https://prolog-api.profy.dev/v2 +NEXT_PUBLIC_API_TOKEN=my-access-token diff --git a/api/axios.ts b/api/axios.ts index cfc0705..7059ca7 100644 --- a/api/axios.ts +++ b/api/axios.ts @@ -1,11 +1,29 @@ import assert from "assert"; -import Axios from "axios"; +import Axios, { AxiosRequestConfig } from "axios"; assert( process.env.NEXT_PUBLIC_API_BASE_URL, "env variable not set: NEXT_PUBLIC_API_BASE_URL" ); +assert( + process.env.NEXT_PUBLIC_API_TOKEN, + "env variable not set: NEXT_PUBLIC_API_TOKEN" +); + +function authRequestInterceptor(config: AxiosRequestConfig) { + if (!config.headers) { + config.headers = {}; + } + + // assertion that env variable exists was already done outside the function + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + config.headers.authorization = process.env.NEXT_PUBLIC_API_TOKEN!; + return config; +} + export const axios = Axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, }); + +axios.interceptors.request.use(authRequestInterceptor); diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx index f343d42..b5044ed 100644 --- a/features/issues/api/use-get-issues.tsx +++ b/features/issues/api/use-get-issues.tsx @@ -17,7 +17,6 @@ async function getIssues(page: number, options?: { signal?: AbortSignal }) { const { data } = await axios.get("/issue", { params: { page, status: "open" }, signal: options?.signal, - headers: { Authorization: "my-access-token" }, }); return data; } diff --git a/features/issues/api/use-resolve-issue.tsx b/features/issues/api/use-resolve-issue.tsx index 0f357ff..1ef9acd 100644 --- a/features/issues/api/use-resolve-issue.tsx +++ b/features/issues/api/use-resolve-issue.tsx @@ -5,11 +5,9 @@ import * as GetIssues from "./use-get-issues"; import type { Issue } from "@features/issues"; async function resolveIssue(issueId: string) { - const { data } = await axios.patch( - `/issue/${issueId}`, - { status: "resolved" }, - { headers: { Authorization: "my-access-token" } } - ); + const { data } = await axios.patch(`/issue/${issueId}`, { + status: "resolved", + }); return data; } From 50366322ac715412286370094566b3c206fbac70 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 12:05:59 +0200 Subject: [PATCH 08/11] Use global query client config --- api/query-client.ts | 7 +++++++ features/issues/api/use-get-issues.tsx | 8 +++----- features/projects/api/use-projects.tsx | 4 +--- pages/_app.tsx | 5 ++--- 4 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 api/query-client.ts diff --git a/api/query-client.ts b/api/query-client.ts new file mode 100644 index 0000000..064d607 --- /dev/null +++ b/api/query-client.ts @@ -0,0 +1,7 @@ +import { QueryClient } from "@tanstack/react-query"; + +const defaultQueryConfig = { staleTime: 60000 }; + +export const queryClient = new QueryClient({ + defaultOptions: { queries: defaultQueryConfig }, +}); diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx index b5044ed..9aee95f 100644 --- a/features/issues/api/use-get-issues.tsx +++ b/features/issues/api/use-get-issues.tsx @@ -25,17 +25,15 @@ export function useGetIssues(page: number) { const query = useQuery, Error>( getQueryKey(page), ({ signal }) => getIssues(page, { signal }), - { staleTime: 60000, keepPreviousData: true } + { keepPreviousData: true } ); // Prefetch the next page! const queryClient = useQueryClient(); useEffect(() => { if (query.data?.meta.hasNextPage) { - queryClient.prefetchQuery( - getQueryKey(page + 1), - ({ signal }) => getIssues(page + 1, { signal }), - { staleTime: 60000 } + queryClient.prefetchQuery(getQueryKey(page + 1), ({ signal }) => + getIssues(page + 1, { signal }) ); } }, [query.data, page, queryClient]); diff --git a/features/projects/api/use-projects.tsx b/features/projects/api/use-projects.tsx index d224bbc..f464302 100644 --- a/features/projects/api/use-projects.tsx +++ b/features/projects/api/use-projects.tsx @@ -8,7 +8,5 @@ async function getProjects() { } export function useProjects() { - return useQuery(["projects"], getProjects, { - staleTime: 60000, - }); + return useQuery(["projects"], getProjects); } diff --git a/pages/_app.tsx b/pages/_app.tsx index 0c49dfd..2542272 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -3,13 +3,12 @@ import "@fontsource/inter/500.css"; import "@fontsource/inter/600.css"; import type { AppProps } from "next/app"; import { ThemeProvider } from "styled-components"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { NavigationProvider } from "@features/ui"; import { GlobalStyle } from "@styles/global-style"; import { theme } from "@styles/theme"; - -const queryClient = new QueryClient(); +import { queryClient } from "@api/query-client"; function MyApp({ Component, pageProps }: AppProps) { return ( From 373af2d3f8bf3aa3bc1a0100f1c1573587c21263 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 12:21:35 +0200 Subject: [PATCH 09/11] Extract api functions --- api/issues.ts | 26 +++++++++++++++++++++++ api/projects.ts | 8 +++++++ features/issues/api/use-get-issues.tsx | 14 +++--------- features/issues/api/use-resolve-issue.tsx | 9 +------- features/projects/api/use-projects.tsx | 7 +----- 5 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 api/issues.ts create mode 100644 api/projects.ts diff --git a/api/issues.ts b/api/issues.ts new file mode 100644 index 0000000..4b893ac --- /dev/null +++ b/api/issues.ts @@ -0,0 +1,26 @@ +import { axios } from "./axios"; + +type IssueFilters = { + status?: "open" | "resolved"; +}; + +const ENDPOINT = "/issue"; + +export async function getIssues( + page: number, + filters: IssueFilters = {}, + options?: { signal?: AbortSignal } +) { + const { data } = await axios.get(ENDPOINT, { + params: { page, ...filters }, + signal: options?.signal, + }); + return data; +} + +export async function resolveIssue(issueId: string) { + const { data } = await axios.patch(`${ENDPOINT}/${issueId}`, { + status: "resolved", + }); + return data; +} diff --git a/api/projects.ts b/api/projects.ts new file mode 100644 index 0000000..ae7b818 --- /dev/null +++ b/api/projects.ts @@ -0,0 +1,8 @@ +import { axios } from "./axios"; + +const ENDPOINT = "/project"; + +export async function getProjects() { + const { data } = await axios.get(ENDPOINT); + return data; +} diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx index 9aee95f..985e42c 100644 --- a/features/issues/api/use-get-issues.tsx +++ b/features/issues/api/use-get-issues.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { axios } from "@api/axios"; +import { getIssues } from "@api/issues"; import type { Page } from "@typings/page.types"; import type { Issue } from "@features/issues"; @@ -13,18 +13,10 @@ export function getQueryKey(page?: number) { return [QUERY_KEY, page]; } -async function getIssues(page: number, options?: { signal?: AbortSignal }) { - const { data } = await axios.get("/issue", { - params: { page, status: "open" }, - signal: options?.signal, - }); - return data; -} - export function useGetIssues(page: number) { const query = useQuery, Error>( getQueryKey(page), - ({ signal }) => getIssues(page, { signal }), + ({ signal }) => getIssues(page, { status: "open" }, { signal }), { keepPreviousData: true } ); @@ -33,7 +25,7 @@ export function useGetIssues(page: number) { useEffect(() => { if (query.data?.meta.hasNextPage) { queryClient.prefetchQuery(getQueryKey(page + 1), ({ signal }) => - getIssues(page + 1, { signal }) + getIssues(page + 1, { status: "open" }, { signal }) ); } }, [query.data, page, queryClient]); diff --git a/features/issues/api/use-resolve-issue.tsx b/features/issues/api/use-resolve-issue.tsx index 1ef9acd..21466f7 100644 --- a/features/issues/api/use-resolve-issue.tsx +++ b/features/issues/api/use-resolve-issue.tsx @@ -1,16 +1,9 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useRef } from "react"; -import { axios } from "@api/axios"; +import { resolveIssue } from "@api/issues"; import * as GetIssues from "./use-get-issues"; import type { Issue } from "@features/issues"; -async function resolveIssue(issueId: string) { - const { data } = await axios.patch(`/issue/${issueId}`, { - status: "resolved", - }); - return data; -} - export function useResolveIssue(page: number) { const queryClient = useQueryClient(); const ongoingMutationCount = useRef(0); diff --git a/features/projects/api/use-projects.tsx b/features/projects/api/use-projects.tsx index f464302..8700ee2 100644 --- a/features/projects/api/use-projects.tsx +++ b/features/projects/api/use-projects.tsx @@ -1,12 +1,7 @@ import { useQuery } from "@tanstack/react-query"; -import { axios } from "@api/axios"; +import { getProjects } from "@api/projects"; import type { Project } from "@features/projects"; -async function getProjects() { - const { data } = await axios.get("/project"); - return data; -} - export function useProjects() { return useQuery(["projects"], getProjects); } From 550b54c02d8db35d516ab76ce23adfc8cb0caac8 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Mon, 17 Oct 2022 13:02:50 +0200 Subject: [PATCH 10/11] Cleanup --- features/issues/api/use-get-issues.tsx | 2 +- features/issues/api/use-resolve-issue.tsx | 2 +- features/issues/components/issue-list/issue-row.tsx | 4 ++-- features/issues/index.ts | 2 +- features/issues/types/{issue.types.ts => index.ts} | 0 features/projects/api/use-projects.tsx | 2 +- .../components/project-card/project-card.stories.tsx | 2 +- features/projects/components/project-card/project-card.tsx | 6 +----- features/projects/components/project-list/project-list.tsx | 2 +- features/projects/index.ts | 2 +- features/projects/types/{project.types.ts => index.ts} | 0 11 files changed, 10 insertions(+), 14 deletions(-) rename features/issues/types/{issue.types.ts => index.ts} (100%) rename features/projects/types/{project.types.ts => index.ts} (100%) diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx index 985e42c..4be8b48 100644 --- a/features/issues/api/use-get-issues.tsx +++ b/features/issues/api/use-get-issues.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { getIssues } from "@api/issues"; import type { Page } from "@typings/page.types"; -import type { Issue } from "@features/issues"; +import type { Issue } from "../types"; const QUERY_KEY = "issues"; diff --git a/features/issues/api/use-resolve-issue.tsx b/features/issues/api/use-resolve-issue.tsx index 21466f7..eac01b4 100644 --- a/features/issues/api/use-resolve-issue.tsx +++ b/features/issues/api/use-resolve-issue.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useRef } from "react"; import { resolveIssue } from "@api/issues"; import * as GetIssues from "./use-get-issues"; -import type { Issue } from "@features/issues"; +import type { Issue } from "../types"; export function useResolveIssue(page: number) { const queryClient = useQueryClient(); diff --git a/features/issues/components/issue-list/issue-row.tsx b/features/issues/components/issue-list/issue-row.tsx index c8ffad6..c7f37d8 100644 --- a/features/issues/components/issue-list/issue-row.tsx +++ b/features/issues/components/issue-list/issue-row.tsx @@ -2,9 +2,9 @@ import styled from "styled-components"; import capitalize from "lodash/capitalize"; import { color, space, textFont } from "@styles/theme"; import { Badge, BadgeColor, BadgeSize } from "@features/ui"; -import { IssueLevel } from "../../types/issue.types"; import { ProjectLanguage } from "@features/projects"; -import type { Issue } from "../../types/issue.types"; +import { IssueLevel } from "../../types"; +import type { Issue } from "../../types"; type IssueRowProps = { projectLanguage: ProjectLanguage; diff --git a/features/issues/index.ts b/features/issues/index.ts index abe4bcf..9227d09 100644 --- a/features/issues/index.ts +++ b/features/issues/index.ts @@ -1,3 +1,3 @@ export * from "./api"; export * from "./components/issue-list"; -export * from "./types/issue.types"; +export * from "./types"; diff --git a/features/issues/types/issue.types.ts b/features/issues/types/index.ts similarity index 100% rename from features/issues/types/issue.types.ts rename to features/issues/types/index.ts diff --git a/features/projects/api/use-projects.tsx b/features/projects/api/use-projects.tsx index 8700ee2..6cd0b07 100644 --- a/features/projects/api/use-projects.tsx +++ b/features/projects/api/use-projects.tsx @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { getProjects } from "@api/projects"; -import type { Project } from "@features/projects"; +import type { Project } from "../types"; export function useProjects() { return useQuery(["projects"], getProjects); diff --git a/features/projects/components/project-card/project-card.stories.tsx b/features/projects/components/project-card/project-card.stories.tsx index 9cc9457..cf7760a 100644 --- a/features/projects/components/project-card/project-card.stories.tsx +++ b/features/projects/components/project-card/project-card.stories.tsx @@ -1,7 +1,7 @@ import React from "react"; import { ComponentStory, ComponentMeta } from "@storybook/react"; import { ProjectCard } from "./project-card"; -import { ProjectLanguage, ProjectStatus } from "@features/projects"; +import { ProjectLanguage, ProjectStatus } from "../../types"; export default { title: "Project/ProjectCard", diff --git a/features/projects/components/project-card/project-card.tsx b/features/projects/components/project-card/project-card.tsx index 5df1a54..2d88eb0 100644 --- a/features/projects/components/project-card/project-card.tsx +++ b/features/projects/components/project-card/project-card.tsx @@ -2,13 +2,9 @@ import Link from "next/link"; import styled from "styled-components"; import capitalize from "lodash/capitalize"; import { Badge, BadgeColor } from "@features/ui"; -import { - Project, - ProjectLanguage, - ProjectStatus, -} from "../../types/project.types"; import { color, displayFont, space, textFont } from "@styles/theme"; import { Routes } from "@config/routes"; +import { Project, ProjectLanguage, ProjectStatus } from "../../types"; type ProjectCardProps = { project: Project; diff --git a/features/projects/components/project-list/project-list.tsx b/features/projects/components/project-list/project-list.tsx index 06dee53..3fd8042 100644 --- a/features/projects/components/project-list/project-list.tsx +++ b/features/projects/components/project-list/project-list.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; +import { breakpoint, space } from "@styles/theme"; import { ProjectCard } from "../project-card"; import { useProjects } from "../../api/use-projects"; -import { breakpoint, space } from "@styles/theme"; const List = styled.ul` display: grid; diff --git a/features/projects/index.ts b/features/projects/index.ts index 0e6a47c..1033b0a 100644 --- a/features/projects/index.ts +++ b/features/projects/index.ts @@ -1,3 +1,3 @@ export * from "./api"; export * from "./components/project-list"; -export * from "./types/project.types"; +export * from "./types"; diff --git a/features/projects/types/project.types.ts b/features/projects/types/index.ts similarity index 100% rename from features/projects/types/project.types.ts rename to features/projects/types/index.ts From d2ae6d5454f140d1d080d5ff1a2b5ff2682359a9 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Wed, 19 Oct 2022 11:13:13 +0200 Subject: [PATCH 11/11] Move types to api folder --- api/issues.ts | 4 +++- features/issues/types/index.ts => api/issues.types.ts | 0 api/projects.ts | 3 ++- features/projects/types/index.ts => api/projects.types.ts | 0 features/issues/api/use-get-issues.tsx | 2 +- features/issues/api/use-resolve-issue.tsx | 2 +- features/issues/components/issue-list/issue-list.tsx | 2 +- features/issues/components/issue-list/issue-row.tsx | 6 +++--- features/issues/index.ts | 1 - features/projects/api/use-projects.tsx | 2 +- .../components/project-card/project-card.stories.tsx | 2 +- features/projects/components/project-card/project-card.tsx | 3 ++- features/projects/index.ts | 1 - 13 files changed, 15 insertions(+), 13 deletions(-) rename features/issues/types/index.ts => api/issues.types.ts (100%) rename features/projects/types/index.ts => api/projects.types.ts (100%) diff --git a/api/issues.ts b/api/issues.ts index 4b893ac..0f6c954 100644 --- a/api/issues.ts +++ b/api/issues.ts @@ -1,4 +1,6 @@ import { axios } from "./axios"; +import type { Issue } from "./issues.types"; +import type { Page } from "@typings/page.types"; type IssueFilters = { status?: "open" | "resolved"; @@ -11,7 +13,7 @@ export async function getIssues( filters: IssueFilters = {}, options?: { signal?: AbortSignal } ) { - const { data } = await axios.get(ENDPOINT, { + const { data } = await axios.get>(ENDPOINT, { params: { page, ...filters }, signal: options?.signal, }); diff --git a/features/issues/types/index.ts b/api/issues.types.ts similarity index 100% rename from features/issues/types/index.ts rename to api/issues.types.ts diff --git a/api/projects.ts b/api/projects.ts index ae7b818..ed1aead 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -1,8 +1,9 @@ import { axios } from "./axios"; +import type { Project } from "./projects.types"; const ENDPOINT = "/project"; export async function getProjects() { - const { data } = await axios.get(ENDPOINT); + const { data } = await axios.get(ENDPOINT); return data; } diff --git a/features/projects/types/index.ts b/api/projects.types.ts similarity index 100% rename from features/projects/types/index.ts rename to api/projects.types.ts diff --git a/features/issues/api/use-get-issues.tsx b/features/issues/api/use-get-issues.tsx index 4be8b48..db82676 100644 --- a/features/issues/api/use-get-issues.tsx +++ b/features/issues/api/use-get-issues.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { getIssues } from "@api/issues"; import type { Page } from "@typings/page.types"; -import type { Issue } from "../types"; +import type { Issue } from "@api/issues.types"; const QUERY_KEY = "issues"; diff --git a/features/issues/api/use-resolve-issue.tsx b/features/issues/api/use-resolve-issue.tsx index eac01b4..b402130 100644 --- a/features/issues/api/use-resolve-issue.tsx +++ b/features/issues/api/use-resolve-issue.tsx @@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useRef } from "react"; import { resolveIssue } from "@api/issues"; import * as GetIssues from "./use-get-issues"; -import type { Issue } from "../types"; +import type { Issue } from "@api/issues.types"; export function useResolveIssue(page: number) { const queryClient = useQueryClient(); diff --git a/features/issues/components/issue-list/issue-list.tsx b/features/issues/components/issue-list/issue-list.tsx index 20d7f04..59ede79 100644 --- a/features/issues/components/issue-list/issue-list.tsx +++ b/features/issues/components/issue-list/issue-list.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import styled from "styled-components"; -import { ProjectLanguage } from "@features/projects"; +import { ProjectLanguage } from "@api/projects.types"; import { color, space, textFont } from "@styles/theme"; import { IssueRow } from "./issue-row"; import { useGetIssues, useResolveIssue } from "../../api"; diff --git a/features/issues/components/issue-list/issue-row.tsx b/features/issues/components/issue-list/issue-row.tsx index c7f37d8..1f030eb 100644 --- a/features/issues/components/issue-list/issue-row.tsx +++ b/features/issues/components/issue-list/issue-row.tsx @@ -2,9 +2,9 @@ import styled from "styled-components"; import capitalize from "lodash/capitalize"; import { color, space, textFont } from "@styles/theme"; import { Badge, BadgeColor, BadgeSize } from "@features/ui"; -import { ProjectLanguage } from "@features/projects"; -import { IssueLevel } from "../../types"; -import type { Issue } from "../../types"; +import { ProjectLanguage } from "@api/projects.types"; +import { IssueLevel } from "@api/issues.types"; +import type { Issue } from "@api/issues.types"; type IssueRowProps = { projectLanguage: ProjectLanguage; diff --git a/features/issues/index.ts b/features/issues/index.ts index 9227d09..8e6318f 100644 --- a/features/issues/index.ts +++ b/features/issues/index.ts @@ -1,3 +1,2 @@ export * from "./api"; export * from "./components/issue-list"; -export * from "./types"; diff --git a/features/projects/api/use-projects.tsx b/features/projects/api/use-projects.tsx index 6cd0b07..9544b73 100644 --- a/features/projects/api/use-projects.tsx +++ b/features/projects/api/use-projects.tsx @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { getProjects } from "@api/projects"; -import type { Project } from "../types"; +import type { Project } from "@api/projects.types"; export function useProjects() { return useQuery(["projects"], getProjects); diff --git a/features/projects/components/project-card/project-card.stories.tsx b/features/projects/components/project-card/project-card.stories.tsx index cf7760a..c0fb30b 100644 --- a/features/projects/components/project-card/project-card.stories.tsx +++ b/features/projects/components/project-card/project-card.stories.tsx @@ -1,7 +1,7 @@ import React from "react"; import { ComponentStory, ComponentMeta } from "@storybook/react"; import { ProjectCard } from "./project-card"; -import { ProjectLanguage, ProjectStatus } from "../../types"; +import { ProjectLanguage, ProjectStatus } from "@api/projects.types"; export default { title: "Project/ProjectCard", diff --git a/features/projects/components/project-card/project-card.tsx b/features/projects/components/project-card/project-card.tsx index 2d88eb0..4034fc2 100644 --- a/features/projects/components/project-card/project-card.tsx +++ b/features/projects/components/project-card/project-card.tsx @@ -4,7 +4,8 @@ import capitalize from "lodash/capitalize"; import { Badge, BadgeColor } from "@features/ui"; import { color, displayFont, space, textFont } from "@styles/theme"; import { Routes } from "@config/routes"; -import { Project, ProjectLanguage, ProjectStatus } from "../../types"; +import { ProjectLanguage, ProjectStatus } from "@api/projects.types"; +import type { Project } from "@api/projects.types"; type ProjectCardProps = { project: Project; diff --git a/features/projects/index.ts b/features/projects/index.ts index 1033b0a..4be9eef 100644 --- a/features/projects/index.ts +++ b/features/projects/index.ts @@ -1,3 +1,2 @@ export * from "./api"; export * from "./components/project-list"; -export * from "./types";