Skip to content

Commit 9ab9b86

Browse files
committed
Add the home page
1 parent 42e18cc commit 9ab9b86

6 files changed

Lines changed: 340 additions & 1 deletion

File tree

src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import AppRouter from './components/AppRouter';
12
import { isSignedIn } from './app/auth';
23
import { Navigate } from 'react-router-dom';
34

45
export default function App() {
56
return (
67
isSignedIn()
7-
? <h2>Home Page</h2>
8+
? <AppRouter />
89
: <Navigate to="/" />
910
);
1011
}

src/components/AppRouter.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { useRoutes } from "react-router-dom";
33
import CircularProgress from "@mui/material/CircularProgress";
44

55
const LazyLoginPage = lazy(() => import('../pages/Login'));
6+
const LazyHomePage = lazy(() => import('../pages/Home'));
7+
8+
export enum RouteName {
9+
Home = '/home',
10+
}
611

712
export default function AppRouter() {
813
return useRoutes([
@@ -14,5 +19,13 @@ export default function AppRouter() {
1419
</Suspense>
1520
),
1621
},
22+
{
23+
path: RouteName.Home,
24+
element: (
25+
<Suspense fallback={<CircularProgress />}>
26+
<LazyHomePage />
27+
</Suspense>
28+
),
29+
}
1730
]);
1831
}

src/components/CommitsTable.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useEffect, useState } from 'react';
2+
import { useLazyGetRepositoryCommitsQuery } from '../features/commits/commitsAPI';
3+
import { DataGrid, GridColumns } from '@mui/x-data-grid';
4+
import { Commit } from '../models/commits';
5+
import Link from '@mui/material/Link';
6+
import Box from '@mui/material/Box';
7+
import Toolbar from '@mui/material/Toolbar';
8+
import Typography from '@mui/material/Typography';
9+
10+
const DEFAULT_ROWS_PER_PAGE = 10;
11+
12+
interface CommitRow {
13+
id: number;
14+
author: string;
15+
committer: string;
16+
message: JSX.Element;
17+
date: string;
18+
}
19+
20+
const commitsTableColumns: GridColumns<CommitRow> = [
21+
{ field: 'author', headerName: 'Author', width: 150 },
22+
{ field: 'committer', headerName: 'Committer', width: 150 },
23+
{ field: 'message', headerName: 'Message', width: 600 },
24+
{ field: 'date', headerName: 'Date', width: 150 },
25+
];
26+
27+
type CommitsTableProps = {
28+
org: string;
29+
repo: string;
30+
}
31+
32+
export default function CommitsTable({ org, repo }: CommitsTableProps) {
33+
const [page, setPage] = useState(0);
34+
const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_ROWS_PER_PAGE);
35+
const [commits, setCommits] = useState<Commit[]>();
36+
const [triggerCommitsQuery] = useLazyGetRepositoryCommitsQuery({ refetchOnReconnect: true });
37+
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]);
49+
50+
const handleChangeRowsPerPage = (newPage: number) => {
51+
setRowsPerPage(newPage);
52+
setPage(0);
53+
};
54+
55+
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"
67+
>
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>
93+
);
94+
}

src/components/Navbar.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import AppBar from '@mui/material/AppBar';
2+
import Box from '@mui/material/Box';
3+
import Toolbar from '@mui/material/Toolbar';
4+
import IconButton from '@mui/material/IconButton';
5+
import Typography from '@mui/material/Typography';
6+
import Menu from '@mui/material/Menu';
7+
import Container from '@mui/material/Container';
8+
import Avatar from '@mui/material/Avatar';
9+
import Tooltip from '@mui/material/Tooltip';
10+
import MenuItem from '@mui/material/MenuItem';
11+
import Link from '@mui/material/Link';
12+
import GitHubIcon from '@mui/icons-material/GitHub';
13+
import LogoutIcon from '@mui/icons-material/Logout';
14+
import { useState } from 'react';
15+
import { User } from '../models/user';
16+
import { signOut } from '../app/auth';
17+
18+
const infoToDisplay: Array<keyof User> = ['email', 'name'];
19+
20+
type NavbarProps = {
21+
user: User;
22+
};
23+
24+
export default function Navbar({ user }: NavbarProps) {
25+
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
26+
27+
return (
28+
<AppBar position="static">
29+
<Container maxWidth="xl">
30+
<Toolbar disableGutters>
31+
<GitHubIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
32+
33+
<Typography
34+
variant="h6"
35+
noWrap
36+
component="a"
37+
href="/"
38+
sx={{
39+
mr: 2,
40+
display: { xs: 'none', md: 'flex' },
41+
fontFamily: 'monospace',
42+
fontWeight: 700,
43+
color: 'inherit',
44+
textDecoration: 'none',
45+
}}
46+
>
47+
Github React App
48+
</Typography>
49+
50+
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }} />
51+
52+
{infoToDisplay.map((info, i) => (
53+
<Box key={i} sx={{ flexGrow: 0, mr: 1 }}>
54+
<Typography sx={{ display: 'block', mr: '0.7rem' }}>
55+
{user[info]}
56+
</Typography>
57+
</Box>
58+
))}
59+
60+
<Box sx={{ flexGrow: 0 }}>
61+
<Tooltip title="Open settings">
62+
<IconButton
63+
onClick={(e) => setAnchorElUser(e.currentTarget)}
64+
sx={{ p: 0 }}
65+
>
66+
<Avatar alt="current_user" src={user.avatar_url} />
67+
</IconButton>
68+
</Tooltip>
69+
70+
<Menu
71+
sx={{ mt: '45px' }}
72+
id="menu-appbar"
73+
anchorEl={anchorElUser}
74+
anchorOrigin={{
75+
vertical: 'top',
76+
horizontal: 'right',
77+
}}
78+
keepMounted
79+
transformOrigin={{
80+
vertical: 'top',
81+
horizontal: 'right',
82+
}}
83+
open={Boolean(anchorElUser)}
84+
onClose={() => setAnchorElUser(null)}
85+
>
86+
<MenuItem key={0}>
87+
<Typography textAlign="center">
88+
<Link href={user.html_url} underline="none" target="_blank">
89+
Profile
90+
</Link>
91+
</Typography>
92+
</MenuItem>
93+
94+
<MenuItem key={1} onClick={() => signOut()}>
95+
<Typography textAlign="center">
96+
Logout
97+
</Typography>
98+
</MenuItem>
99+
</Menu>
100+
</Box>
101+
102+
<Box sx={{ flexGrow: 0, ml: 1 }}>
103+
<Tooltip title="Logout">
104+
<IconButton onClick={() => signOut()}>
105+
<LogoutIcon />
106+
</IconButton>
107+
</Tooltip>
108+
</Box>
109+
</Toolbar>
110+
</Container>
111+
</AppBar>
112+
);
113+
}

src/components/RepositoryList.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { useEffect, useState } from 'react';
2+
import { Repository } from '../models/repository';
3+
import { useLazyGetRepositoriesQuery } from '../features/repositories/repositoriesAPI';
4+
import Box from '@mui/material/Box';
5+
import TextField from '@mui/material/TextField';
6+
import InputLabel from '@mui/material/InputLabel';
7+
import Select, { SelectChangeEvent } from '@mui/material/Select';
8+
import FormControl from '@mui/material/FormControl';
9+
import MenuItem from '@mui/material/MenuItem';
10+
11+
type RepositoryListProps = {
12+
setRepoName: (name: string) => void;
13+
setOrgName: (name: string) => void;
14+
};
15+
16+
export default function RepositoryList({ setRepoName, setOrgName }: RepositoryListProps) {
17+
const [org, setOrg] = useState('');
18+
const [repo, setRepo] = useState('');
19+
const [repos, setRepos] = useState<Repository[]>();
20+
const [selectDisabled, setSelectDisabled] = useState(true);
21+
const [triggerRepoQuery] = useLazyGetRepositoriesQuery({ refetchOnReconnect: true });
22+
23+
useEffect(() => {
24+
const searchForRepos = setTimeout(async () => {
25+
if (org.length > 1) {
26+
const { data } = await triggerRepoQuery(org, true);
27+
setRepos(data?.repositories);
28+
setSelectDisabled(false);
29+
};
30+
}, 1500);
31+
return () => clearTimeout(searchForRepos);
32+
}, [org, triggerRepoQuery]);
33+
34+
const handleOrgInput = (e: React.ChangeEvent<HTMLInputElement>) => {
35+
const name = e.target.value;
36+
setOrg(name);
37+
setOrgName(name);
38+
};
39+
40+
const handleSelectChange = (e: SelectChangeEvent<string>) => {
41+
const name = e.target.value as string;
42+
setRepo(name);
43+
setRepoName(name);
44+
};
45+
46+
return (
47+
<Box
48+
component="form"
49+
sx={{
50+
'& > :not(style)': { m: 1, width: '25ch' },
51+
m: '2rem',
52+
}}
53+
>
54+
<TextField
55+
id="outlined-basic"
56+
label="Organization"
57+
variant="outlined"
58+
color="primary"
59+
sx={{ my: 2 }}
60+
value={org}
61+
onChange={handleOrgInput}
62+
/>
63+
{/* TODO: make a loading spinner */}
64+
<FormControl sx={{ m: 1, minWidth: 120 }} disabled={selectDisabled}>
65+
<InputLabel id="select-repo-label">Repositories</InputLabel>
66+
<Select
67+
label="Repositories"
68+
labelId="select-repo-label"
69+
value={repo}
70+
onChange={handleSelectChange}
71+
>
72+
{repos && repos.map((repository, i) => (
73+
<MenuItem key={i} value={repository.name}>
74+
{repository.name}
75+
</MenuItem>
76+
))}
77+
</Select>
78+
</FormControl>
79+
</Box>
80+
);
81+
}

src/pages/Home.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useEffect, useState } from "react";
2+
import { Navigate } from "react-router-dom";
3+
import { isSignedIn } from "../app/auth";
4+
import { useLazyGetCurrentUserQuery } from "../features/user/userAPI";
5+
import { User } from "../models/user";
6+
import Navbar from "../components/Navbar";
7+
import RepositoryList from "../components/RepositoryList";
8+
import CommitsTable from "../components/CommitsTable";
9+
10+
export default function HomePage() {
11+
const [user, setUser] = useState<User>();
12+
const [org, setOrg] = useState('');
13+
const [repo, setRepo] = useState('');
14+
const [triggerUserQuery] = useLazyGetCurrentUserQuery({ refetchOnReconnect: true });
15+
16+
useEffect(() => {
17+
triggerUserQuery(undefined, true)
18+
.then(({ data }) => setUser(data))
19+
.catch(console.error);
20+
}, [triggerUserQuery]);
21+
22+
return (
23+
isSignedIn()
24+
? <>
25+
{user && <Navbar user={user} />}
26+
27+
<RepositoryList
28+
setRepoName={setRepo}
29+
setOrgName={setOrg}
30+
/>
31+
32+
<CommitsTable org={org} repo={repo} />
33+
</>
34+
: <Navigate to="/" />
35+
);
36+
}
37+

0 commit comments

Comments
 (0)