diff --git a/public/loading.webp b/public/loading.webp
new file mode 100644
index 0000000..ac95e35
Binary files /dev/null and b/public/loading.webp differ
diff --git a/src/App.tsx b/src/App.tsx
index ba65e6e..c222108 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,7 +6,10 @@ import { SettingsDrawer } from "./SettingsDrawer";
import { Dashboard } from "./components/Dashboard";
import { AuthHeader } from "./components/AuthHeader";
import { UnAuthHeader } from "./components/UnAuthHeader";
-import LandingPage from "./components/LandingPage";
+import LandingPage from "./pages/LandingPage";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+const queryClient = new QueryClient();
export const ConfigContext = React.createContext<{
octokit: GitService | null;
@@ -27,8 +30,7 @@ function App() {
const onLogin = React.useCallback(() => {
if (token) {
const octoKit = new GitService(
- process.env.REACT_APP_GITHUB_API_URL ||
- "https://api.github.com/",
+ process.env.REACT_APP_GITHUB_API_URL || "https://api.github.com/",
token
);
octoKit.testAuthentication().then((user) => {
@@ -91,45 +93,51 @@ function App() {
return (
<>
-
-
-
-
- {!user?.login ? (
-
- ) : (
-
- )}
-
-
-
+
+
- {octokit && }
- {!octokit && }
-
- {octokit && (
- setOpenSettings(false)}
- />
- )}
-
-
+
+
+ {!user?.login ? (
+
+ ) : (
+
+ )}
+
+
+
+ {octokit && }
+ {!octokit && }
+
+ {octokit && (
+ setOpenSettings(false)}
+ />
+ )}
+
+
+
>
);
}
diff --git a/src/SettingsDrawer.tsx b/src/SettingsDrawer.tsx
index 5d53f4d..549d598 100644
--- a/src/SettingsDrawer.tsx
+++ b/src/SettingsDrawer.tsx
@@ -3,6 +3,7 @@ import React from "react";
import { Organization } from "./models/Organization";
import { ConfigContext } from "./App";
import { RepoSettingAccordion } from "./components/RepoSettingAccordion";
+import { useQuery } from "@tanstack/react-query";
export type SettingsDrawerProps = {
opened: boolean;
@@ -13,12 +14,16 @@ export const SettingsDrawer: React.FC = ({
opened,
onClose,
}) => {
- const [orgs, setOrgs] = React.useState([]);
const { octokit } = React.useContext(ConfigContext);
-
- React.useEffect(() => {
- if(octokit) octokit?.getOrganizations().then((orgs) => setOrgs(orgs.data));
- }, [octokit]);
+ const { data: orgs = [] } = useQuery({
+ queryKey: ["orgs"],
+ queryFn: async () => {
+ if (!octokit) return;
+ const orgs = await octokit.getOrganizations();
+ return orgs.data as Organization[];
+ },
+ enabled: !!octokit,
+ });
const orgList = React.useMemo(() => orgs.map((org) => (
diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx
index efe6b09..7764848 100644
--- a/src/components/Dashboard.tsx
+++ b/src/components/Dashboard.tsx
@@ -4,40 +4,38 @@ import { PullRequest } from "../models/PullRequest";
import PullRequestCard from "./PullRequestCard";
import { Box } from "@mui/material";
import Grid2 from "@mui/material/Unstable_Grid2/Grid2";
-import LandingPage from "./LandingPage";
+import LandingPage from "../pages/LandingPage";
import { MultiselectFilter } from "./MultiselectFilter";
import { InputFilter } from "./InputFilter";
+import { useQuery } from "@tanstack/react-query";
+import { PRLoadingPage } from "../pages/PRLoadingPage";
export type DashboardProps = {};
export const Dashboard: React.FC = () => {
const { octokit, repositorySettings } = React.useContext(ConfigContext);
- const [pulls, setPulls] = React.useState([]);
const activeRepositories = React.useMemo(
() =>
Object.keys(repositorySettings).filter((key) => repositorySettings[key]),
[repositorySettings]
);
- React.useEffect(() => {
- if (octokit && activeRepositories.length) {
- const getPulls = async () => {
+ const { data = [], isLoading } = useQuery({
+ queryKey: ["pulls"],
+ queryFn: async () => {
+ if (octokit && activeRepositories.length) {
const pulls = await Promise.all(
activeRepositories.flatMap((repo) => octokit.getPullRequests(repo))
);
- setPulls(
- pulls
- .flat()
- .sort((a, b) =>
- a.base.repo.full_name.localeCompare(b.base.repo.full_name)
- ) as any[]
- );
- };
-
- getPulls();
- }
- }, [octokit, activeRepositories]);
+ return pulls
+ .flat()
+ .sort((a, b) =>
+ a.base.repo.full_name.localeCompare(b.base.repo.full_name)
+ );
+ }
+ },
+ });
const [filter, setFilter] = React.useState("");
const [includeLabels, setIncludeLabels] = React.useState([]);
@@ -45,13 +43,13 @@ export const Dashboard: React.FC = () => {
const labels: string[] = React.useMemo(
() =>
Array.from(
- new Set(pulls.map((pull) => pull.labels.map(({ name }) => name)).flat())
+ new Set(data.map((pull) => pull.labels.map(({ name }) => name)).flat())
),
- [pulls]
+ [data]
);
const filteredPulls = React.useMemo(() => {
- return pulls.filter((pull) => {
+ return data.filter((pull) => {
if (
includeLabels.length > 0 &&
!pull.labels.some(({ name }) => includeLabels.includes(name))
@@ -64,7 +62,7 @@ export const Dashboard: React.FC = () => {
return false;
if (
filter.length > 0 &&
- !pull.user.login
+ !pull.user?.login
.toLocaleLowerCase()
.includes(filter.toLocaleLowerCase()) &&
!pull.head.repo.full_name
@@ -75,34 +73,36 @@ export const Dashboard: React.FC = () => {
return true;
});
- }, [pulls, filter, includeLabels, excludeLabels]);
+ }, [data, filter, includeLabels, excludeLabels]);
return (
- {pulls.length === 0 ? (
-
- ) : (
-
-
- !excludeLabels.includes(label))}
- name="Include labels"
- onChange={setIncludeLabels}
- />
- !includeLabels.includes(label))}
- name="Exclude labels"
- onChange={setExcludeLabels}
- />
-
- )}
-
- {filteredPulls.map((pull) => (
-
-
+ {isLoading && }
+ {!isLoading && data.length === 0 && }
+ {data.length > 0 && (
+ <>
+
+
+ !excludeLabels.includes(label))}
+ name="Include labels"
+ onChange={setIncludeLabels}
+ />
+ !includeLabels.includes(label))}
+ name="Exclude labels"
+ onChange={setExcludeLabels}
+ />
+
+
+ {filteredPulls.map((pull) => (
+
+
+
+ ))}
- ))}
-
+ >
+ )}
);
};
diff --git a/src/components/PullRequestChecks.tsx b/src/components/PullRequestChecks.tsx
index f0fd529..7de8fc9 100644
--- a/src/components/PullRequestChecks.tsx
+++ b/src/components/PullRequestChecks.tsx
@@ -1,9 +1,17 @@
import React from "react";
import { ConfigContext } from "../App";
import { CheckRun } from "../models/CheckRun";
-import { Box, Dialog, Link, Tooltip, Typography } from "@mui/material";
+import {
+ Box,
+ CircularProgress,
+ Dialog,
+ Link,
+ Tooltip,
+ Typography,
+} from "@mui/material";
import { CheckCircle, Error, ErrorOutline } from "@mui/icons-material";
import { useOnScreen } from "../hooks/useOnScreen";
+import { useQuery } from "@tanstack/react-query";
export type PullRequestChecksProps = {
owner: string;
@@ -17,24 +25,22 @@ export const PullRequestChecks: React.FC = ({
prNumber,
}) => {
const { octokit } = React.useContext(ConfigContext);
- const [checks, setChecks] = React.useState();
const [open, setOpen] = React.useState(false);
const elementRef = React.useRef(null);
const isIntersecting = useOnScreen(elementRef, "100px", true);
- React.useEffect(() => {
- if (!octokit || !isIntersecting) return;
- octokit
- .getPRChecksStatus(owner, repo, prNumber)
- .then((response) => setChecks(response.data.check_runs));
-
- return () => {
- setChecks(undefined);
- };
- }, [octokit, owner, repo, prNumber, isIntersecting]);
+ const { isLoading, data: checks = [] } = useQuery({
+ queryKey: ["checks", owner, repo, prNumber],
+ queryFn: async () => {
+ if (!octokit || !isIntersecting) return;
+ const response = await octokit.getPRChecksStatus(owner, repo, prNumber);
+ return response.data.check_runs as CheckRun[];
+ },
+ enabled: !!octokit && isIntersecting,
+ });
const allChecksPassed = React.useMemo(
- () => checks?.every((check) => check.conclusion === "success"),
+ () => checks.every((check) => check.conclusion === "success"),
[checks]
);
@@ -44,41 +50,59 @@ export const PullRequestChecks: React.FC = ({
ref={elementRef}
color="text.secondary"
onClick={() => !allChecksPassed && setOpen(true)}
- sx={{ display: "flex", gap: 1, alignItems: "center", cursor: allChecksPassed ? "default" : "pointer" }}
+ sx={{
+ display: "flex",
+ gap: 1,
+ alignItems: "center",
+ cursor: allChecksPassed ? "default" : "pointer",
+ }}
>
Checks:{" "}
- {allChecksPassed ? (
-
+ {isLoading ? (
+
+ ) : allChecksPassed ? (
+
+
+
) : (
-
+
+
+
)}
-