Skip to content

Commit 68696cb

Browse files
committed
Update the components and logic
1 parent 9ab9b86 commit 68696cb

28 files changed

Lines changed: 477 additions & 572 deletions

src/App.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import AppRouter from './components/AppRouter';
2-
import { isSignedIn } from './app/auth';
3-
import { Navigate } from 'react-router-dom';
42

53
export default function App() {
6-
return (
7-
isSignedIn()
8-
? <AppRouter />
9-
: <Navigate to="/" />
10-
);
4+
return <AppRouter />;
115
}

src/app/auth.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { AxiosInstance } from "axios";
2-
import { isExpiredTokenResponse } from "./axios";
3-
import { LocalStorageKeys } from "./const";
4-
import { ErrorCode, LogicError } from "./errors";
1+
import { AxiosInstance } from 'axios';
2+
import { isExpiredTokenResponse } from './axios';
3+
import { LocalStorageKeys } from './const';
4+
import { ErrorCode, LogicError } from './errors';
55

66
export interface AccessTokenResponse {
77
accessToken: {
@@ -15,7 +15,10 @@ export function setAccessToken(token: string) {
1515
localStorage.setItem(LocalStorageKeys.AccessToken, token);
1616
}
1717

18-
// TODO: redirect to the login page
18+
export function setCode(code: string) {
19+
localStorage.setItem(LocalStorageKeys.Code, code);
20+
}
21+
1922
export function signOut() {
2023
localStorage.removeItem(LocalStorageKeys.AccessToken);
2124
localStorage.removeItem(LocalStorageKeys.Code);
@@ -28,7 +31,7 @@ export function isSignedIn() {
2831
export async function refreshAccessToken(client: AxiosInstance) {
2932
try {
3033
const { data } = await client.post<AccessTokenResponse>('/auth', { code: getAuthCode() });
31-
localStorage.setItem(LocalStorageKeys.AccessToken, data.accessToken.access_token);
34+
setAccessToken(data.accessToken.access_token);
3235
} catch (error) {
3336
if (isExpiredTokenResponse(error)) {
3437
signOut();

src/app/axios.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
2-
import { getAccessToken, refreshAccessToken } from "./auth";
3-
import { ServerErrorCode } from "./errors";
1+
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios';
2+
import { getAccessToken, refreshAccessToken } from './auth';
3+
import { ServerErrorCode } from './errors';
44

55
export interface AxiosClient {
66
protectedClient: AxiosInstance;
@@ -19,21 +19,25 @@ export function createAxiosClient(baseUrl: string): AxiosClient {
1919
req.headers = {};
2020
}
2121
req.headers.Authorization = 'Bearer ' + getAccessToken();
22+
return req;
2223
});
2324

24-
protectedClient.interceptors.response.use((res) => res, async (error) => {
25-
if (isExpiredTokenResponse(error)) {
26-
await refreshAccessToken(publicClient);
27-
return protectedClient.request(error.config);
25+
protectedClient.interceptors.response.use(
26+
(res) => res,
27+
async (error) => {
28+
if (isExpiredTokenResponse(error)) {
29+
await refreshAccessToken(publicClient);
30+
return protectedClient.request(error.config);
31+
}
32+
throw error;
2833
}
29-
throw error;
30-
});
34+
);
3135

3236
return {
3337
protectedClient,
34-
publicClient
38+
publicClient,
3539
};
36-
};
40+
}
3741

3842
export function isExpiredTokenResponse(error: unknown): error is AxiosError {
3943
return (

src/app/config.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Nullable } from "./types";
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2+
3+
import { Nullable } from './types';
24

35
export interface Config {
46
githubClientId: string;
@@ -7,7 +9,7 @@ export interface Config {
79
redirectUrl: string;
810
}
911

10-
let config: Readonly<Nullable<Config>>;
12+
let config: Readonly<Nullable<Config>> = null;
1113

1214
export function getConfig() {
1315
if (!config) {
@@ -29,4 +31,4 @@ export function getConfig() {
2931
};
3032
}
3133
return config;
32-
};
34+
}

src/app/errors.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { SerializedError } from '@reduxjs/toolkit';
2+
13
export enum ErrorCode {
24
NotSignedIn = 'signIn.no',
35
UserNotLoaded = 'user.not.loaded',
@@ -18,4 +20,8 @@ export class LogicError extends Error {
1820
constructor(public readonly code: ErrorCode, message?: string, cause?: Error) {
1921
super(message, cause);
2022
}
21-
}
23+
}
24+
25+
export function isSerializedError(error: any): error is SerializedError {
26+
return 'code' in error && 'message' in error;
27+
}

src/app/store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import { branchesApi } from '../features/branch/branchAPI';
44
import { commitsApi } from '../features/commits/commitsAPI';
55
import { repositoriesApi } from '../features/repositories/repositoriesAPI';
66
import { userApi } from '../features/user/userAPI';
7+
import organizationReducer from '../features/organization/organizationSlice';
8+
import repositoryReducer from '../features/repositories/repositorySlice';
79

810
export const store = configureStore({
911
reducer: {
12+
orgReducer: organizationReducer,
13+
repoReducer: repositoryReducer,
1014
[branchesApi.reducerPath]: branchesApi.reducer,
1115
[repositoriesApi.reducerPath]: repositoriesApi.reducer,
1216
[commitsApi.reducerPath]: commitsApi.reducer,

src/app/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export type Nullable<T> = T | null;
1+
export type Nullable<T> = T | null;
2+
export type Optional<T> = T | undefined;

src/components/AppRouter.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { lazy, Suspense } from "react";
2-
import { useRoutes } from "react-router-dom";
3-
import CircularProgress from "@mui/material/CircularProgress";
1+
import { lazy, Suspense } from 'react';
2+
import { useRoutes } from 'react-router-dom';
3+
import LoginPage from '../pages/Login/Login';
4+
import CircularProgress from '@mui/material/CircularProgress';
45

5-
const LazyLoginPage = lazy(() => import('../pages/Login'));
6-
const LazyHomePage = lazy(() => import('../pages/Home'));
6+
const LazyHomePage = lazy(() => import('../pages/Home/Home'));
77

88
export enum RouteName {
99
Home = '/home',
@@ -13,11 +13,7 @@ export default function AppRouter() {
1313
return useRoutes([
1414
{
1515
index: true,
16-
element: (
17-
<Suspense fallback={<CircularProgress />}>
18-
<LazyLoginPage />
19-
</Suspense>
20-
),
16+
element: <LoginPage />,
2117
},
2218
{
2319
path: RouteName.Home,
@@ -26,6 +22,6 @@ export default function AppRouter() {
2622
<LazyHomePage />
2723
</Suspense>
2824
),
29-
}
25+
},
3026
]);
3127
}

src/components/CommitsTable.tsx

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,101 @@
1-
import { useEffect, useState } from 'react';
2-
import { useLazyGetRepositoryCommitsQuery } from '../features/commits/commitsAPI';
1+
import { FC, useState } from 'react';
32
import { DataGrid, GridColumns } from '@mui/x-data-grid';
43
import { Commit } from '../models/commits';
5-
import Link from '@mui/material/Link';
4+
import { useAppSelector } from '../app/hooks';
5+
import { useCommits } from '../hooks/useCommits';
66
import Box from '@mui/material/Box';
77
import Toolbar from '@mui/material/Toolbar';
88
import Typography from '@mui/material/Typography';
9+
import Container from '@mui/material/Container';
910

1011
const DEFAULT_ROWS_PER_PAGE = 10;
1112

1213
interface CommitRow {
1314
id: number;
1415
author: string;
1516
committer: string;
16-
message: JSX.Element;
17+
message: string;
1718
date: string;
1819
}
1920

2021
const commitsTableColumns: GridColumns<CommitRow> = [
2122
{ field: 'author', headerName: 'Author', width: 150 },
2223
{ field: 'committer', headerName: 'Committer', width: 150 },
23-
{ field: 'message', headerName: 'Message', width: 600 },
24-
{ field: 'date', headerName: 'Date', width: 150 },
24+
{ field: 'message', headerName: 'Message', width: 650 },
25+
{ field: 'date', headerName: 'Date', width: 175 },
2526
];
2627

2728
type CommitsTableProps = {
2829
org: string;
29-
repo: string;
30-
}
30+
};
3131

32-
export default function CommitsTable({ org, repo }: CommitsTableProps) {
32+
const CommitsTable: FC<CommitsTableProps> = ({ org }) => {
3333
const [page, setPage] = useState(0);
3434
const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_ROWS_PER_PAGE);
35-
const [commits, setCommits] = useState<Commit[]>();
36-
const [triggerCommitsQuery] = useLazyGetRepositoryCommitsQuery({ refetchOnReconnect: true });
3735

38-
useEffect(() => {
39-
const searchForCommits = setTimeout(async () => {
40-
const { data } = await triggerCommitsQuery({
41-
username: org,
42-
repository: repo
43-
}, true);
44-
setCommits(data?.commits);
45-
}, 600);
46-
return () => clearTimeout(searchForCommits);
47-
// eslint-disable-next-line react-hooks/exhaustive-deps
48-
}, [repo, triggerCommitsQuery]);
36+
const repo = useAppSelector((state) => state.repoReducer.name);
37+
const [commits, isLoading, , error, isUninitialized] = useCommits(org, repo);
4938

5039
const handleChangeRowsPerPage = (newPage: number) => {
5140
setRowsPerPage(newPage);
5241
setPage(0);
5342
};
5443

5544
return (
56-
<Box sx={{ height: 400, width: '100%' }}>
57-
{commits && (<>
58-
<Toolbar sx={{
59-
pl: { sm: 2 },
60-
pr: { xs: 1, sm: 1 },
61-
}}>
62-
<Typography
63-
sx={{ flex: '1 1 100%' }}
64-
variant="h6"
65-
id="tableTitle"
66-
component="div"
45+
<Container maxWidth="xl">
46+
<Box sx={{ height: 400, width: '100%' }}>
47+
{!isUninitialized && (
48+
<Toolbar
49+
sx={{
50+
pl: { sm: 2 },
51+
pr: { xs: 1, sm: 1 },
52+
}}
6753
>
68-
Commits List
69-
</Typography>
70-
</Toolbar>
71-
{/* TODO: error / loading */}
72-
<DataGrid
73-
disableSelectionOnClick
74-
page={page}
75-
pageSize={rowsPerPage}
76-
rowsPerPageOptions={[5, 10, 15]}
77-
onPageChange={(newPage) => setPage(newPage)}
78-
onPageSizeChange={handleChangeRowsPerPage}
79-
density="compact"
80-
rows={commits.map((commit, i) => ({
81-
id: i,
82-
author: commit.commit.author.name,
83-
committer: commit.commit.committer.name,
84-
message: (
85-
<Link href={commit.html_url}>{commit.commit.message}</Link>
86-
),
87-
date: commit.commit.committer.date,
88-
}))}
89-
columns={commitsTableColumns}
90-
/>
91-
</>)}
92-
</Box>
54+
<Typography
55+
sx={{
56+
flex: '1 1 100%',
57+
alignItems: 'center',
58+
alignContent: 'center',
59+
justifyContent: 'center',
60+
justifyItems: 'center',
61+
}}
62+
variant="h6"
63+
id="tableTitle"
64+
component="div"
65+
>
66+
Commits List
67+
</Typography>
68+
</Toolbar>
69+
)}
70+
{commits ? (
71+
<DataGrid
72+
disableSelectionOnClick
73+
page={page}
74+
pageSize={rowsPerPage}
75+
rowsPerPageOptions={[5, 10, 15]}
76+
onPageChange={(newPage) => setPage(newPage)}
77+
onPageSizeChange={handleChangeRowsPerPage}
78+
density="compact"
79+
rows={mapCommits(commits)}
80+
columns={commitsTableColumns}
81+
loading={isLoading}
82+
/>
83+
) : (
84+
<DataGrid columns={commitsTableColumns} rows={[]} error={error} loading={isLoading} />
85+
)}
86+
</Box>
87+
</Container>
9388
);
94-
}
89+
};
90+
91+
export function mapCommits(commits: Commit[]) {
92+
return commits.map((commit, i) => ({
93+
id: i,
94+
author: commit.commit.author.name,
95+
committer: commit.commit.committer.name,
96+
message: commit.commit.message,
97+
date: commit.commit.committer.date,
98+
}));
99+
}
100+
101+
export default CommitsTable;

0 commit comments

Comments
 (0)