Skip to content

Commit b59eb03

Browse files
committed
[add] GitHub-reward issue model, card & page
[optimize] upgrade to MUI 7 [fix] Third-party URL bug of Next.js Image component (vercel/next.js#53715)
1 parent 8f92d0d commit b59eb03

File tree

9 files changed

+260
-124
lines changed

9 files changed

+260
-124
lines changed

components/Git/Issue/Card.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Avatar, Card, CardContent, CardProps, Chip, Icon,Stack, Typography } from '@mui/material';
2+
import { marked } from 'marked';
3+
import { Issue } from 'mobx-github';
4+
import { FC } from 'react';
5+
6+
export type IssueCardProps = Issue & Omit<CardProps, 'id'>;
7+
8+
export const IssueCard: FC<IssueCardProps> = ({
9+
id,
10+
number,
11+
title,
12+
labels,
13+
body,
14+
html_url,
15+
user,
16+
comments,
17+
created_at,
18+
...props
19+
}) => (
20+
<Card {...props}>
21+
<CardContent>
22+
<Typography
23+
variant="h4"
24+
component="a"
25+
href={html_url}
26+
target="_blank"
27+
rel="noreferrer"
28+
style={{ textDecoration: 'none', color: 'inherit' }}
29+
>
30+
#{number} {title}
31+
</Typography>
32+
33+
<Stack direction="row" spacing={1}>
34+
{labels?.map(
35+
label =>
36+
typeof label === 'object' && (
37+
<Chip
38+
key={label.name}
39+
label={label.name}
40+
style={{ backgroundColor: `#${label.color || 'e0e0e0'}` }}
41+
/>
42+
),
43+
)}
44+
</Stack>
45+
46+
<Typography component="article" dangerouslySetInnerHTML={{ __html: marked(body || '') }} />
47+
48+
{user && (
49+
<Stack direction="row" spacing={1} alignItems="center">
50+
<Avatar src={user.avatar_url} alt={user.name || ''} />
51+
<Typography>{user.name || ''}</Typography>
52+
</Stack>
53+
)}
54+
<Stack direction="row" spacing={1} alignItems="center">
55+
<Icon>chat_bubble_outline</Icon>
56+
{comments}
57+
</Stack>
58+
59+
<time dateTime={created_at}>{new Date(created_at).toLocaleString()}</time>
60+
</CardContent>
61+
</Card>
62+
);

components/Git/Issue/Panel.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {
2+
Accordion,
3+
AccordionDetails,
4+
AccordionSummary,
5+
Badge,
6+
Chip,
7+
Grid,
8+
Icon,
9+
Typography,
10+
} from '@mui/material';
11+
import type { GitRepository } from 'mobx-github';
12+
import { FC } from 'react';
13+
14+
import { IssueCard } from './Card';
15+
16+
export const IssuePanel: FC<GitRepository> = ({ name, language, issues }) => (
17+
<Accordion>
18+
<AccordionSummary expandIcon={<Icon>expand_more</Icon>}>
19+
<Grid container alignItems="center" spacing={2}>
20+
<Grid size={{ xs: 4, sm: 2 }}>{language && <Chip label={language} />}</Grid>
21+
<Grid size={{ xs: 6, sm: 8 }}>
22+
<Typography variant="h6" noWrap>
23+
{name}
24+
</Typography>
25+
</Grid>
26+
<Grid size={{ xs: 2 }} textAlign="right">
27+
<Badge sx={{ fontSize: '1rem', backgroundColor: 'info.main' }}>{issues?.length}</Badge>
28+
</Grid>
29+
</Grid>
30+
</AccordionSummary>
31+
32+
<AccordionDetails>
33+
<Grid container spacing={3}>
34+
{issues?.map(issue => (
35+
<Grid key={issue.title} size={{ xs: 12, sm: 6, xl: 6 }}>
36+
<IssueCard className="h-full" {...issue} />
37+
</Grid>
38+
))}
39+
</Grid>
40+
</AccordionDetails>
41+
</Accordion>
42+
);

components/Member/Card.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { CardProps, Chip } from '@mui/material';
22
import { marked } from 'marked';
33
import { observer } from 'mobx-react';
4-
import Image from 'next/image';
54
import Link from 'next/link';
65
import { FC } from 'react';
76

@@ -29,15 +28,13 @@ export const MemberCard: FC<MemberCardProps> = observer(
2928

3029
<div className="flex w-auto items-center gap-4">
3130
{github && (
32-
<Image
33-
width={64}
34-
height={64}
31+
<img
32+
style={{ width: '4rem', height: '4rem' }}
3533
className="rounded-full object-cover"
3634
src={`https://github.com/${String(github)}.png`}
3735
alt={String(github)}
3836
/>
3937
)}
40-
4138
<Link href={`/member/${String(nickname)}`} aria-label={String(nickname)}>
4239
<h2 className="text-base">{String(nickname)}</h2>
4340
<p className="text-sm">{String(position ?? '')}</p>

models/Base.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import { HTTPClient } from 'koajax';
22
import MIME from 'mime';
3+
import { githubClient } from 'mobx-github';
34
import { TableCellValue, TableCellMedia, TableCellAttachment } from 'mobx-lark';
45

5-
import { API_Host } from './configuration';
6+
import { API_Host, GithubToken, isServer } from './configuration';
7+
8+
if (!isServer()) githubClient.baseURI = `${API_Host}/api/GitHub/`;
9+
10+
githubClient.use(({ request }, next) => {
11+
if (GithubToken)
12+
request.headers = {
13+
authorization: `Bearer ${GithubToken}`,
14+
...request.headers,
15+
};
16+
return next();
17+
});
18+
19+
export { githubClient };
620

721
export const larkClient = new HTTPClient({
822
baseURI: `${API_Host}/api/Lark/`,

models/Issue.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { isEmpty } from 'lodash';
2+
import { Issue } from 'mobx-github';
3+
import { Filter, ListModel } from 'mobx-restful';
4+
import { buildURLData } from 'web-utility';
5+
6+
import { githubClient } from './Base';
7+
8+
interface SearchData<T> {
9+
total_count: number;
10+
incomplete_results: boolean;
11+
items: T[];
12+
}
13+
14+
export type IssueFilter = Filter<Issue>;
15+
16+
export class IssueModel extends ListModel<Issue, IssueFilter> {
17+
baseURI = 'search/issues';
18+
client = githubClient;
19+
20+
async loadPage(
21+
page = this.pageIndex,
22+
per_page = this.pageSize,
23+
{ repository_url, state, title }: IssueFilter,
24+
) {
25+
const [org, repo] = repository_url?.replace('https://github.com/', '').split('/') || [];
26+
27+
const condition = Object.entries({ org, repo, state })
28+
.filter(([, value]) => !isEmpty(value))
29+
.map(([key, value]) => `${key}:${value}`)
30+
.join(' ');
31+
32+
const { body } = await this.client.get<SearchData<Issue>>(
33+
`${this.baseURI}?${buildURLData({ page, per_page, q: `${condition} ${title}` })}`,
34+
);
35+
return { pageData: body!.items, totalCount: body!.total_count };
36+
}
37+
}
38+
39+
export default new IssueModel();

models/Repository.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
import { observable } from 'mobx';
2-
import { githubClient, GitRepository, RepositoryFilter, RepositoryModel } from 'mobx-github';
2+
import { GitRepository, RepositoryFilter, RepositoryModel } from 'mobx-github';
33
import { Stream } from 'mobx-restful';
44

5-
import { API_Host, GithubToken, isServer } from './configuration';
6-
7-
if (!isServer()) githubClient.baseURI = `${API_Host}/api/GitHub/`;
8-
9-
githubClient.use(({ request }, next) => {
10-
if (GithubToken)
11-
request.headers = {
12-
authorization: `Bearer ${GithubToken}`,
13-
...request.headers,
14-
};
15-
return next();
16-
});
5+
import { githubClient } from './Base';
176

187
export class GitRepositoryModel extends Stream<GitRepository, RepositoryFilter>(RepositoryModel) {
198
client = githubClient;

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
"@giscus/react": "^3.1.0",
1313
"@koa/bodyparser": "^5.1.1",
1414
"@koa/router": "^13.1.0",
15-
"@mui/lab": "6.0.0-beta.31",
16-
"@mui/material": "^6.4.11",
15+
"@mui/lab": "^7.0.0-beta.11",
16+
"@mui/material": "^7.0.2",
1717
"@sentry/nextjs": "^9.15.0",
1818
"file-type": "^20.5.0",
1919
"jsonwebtoken": "^9.0.2",

pages/project/reward/issue.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Grid } from '@mui/material';
2+
import { Issue } from 'mobx-github';
3+
import { observer } from 'mobx-react';
4+
import { cache, compose, errorLogger, translator } from 'next-ssr-middleware';
5+
import { FC } from 'react';
6+
7+
import { IssueCard } from '../../../components/Git/Issue/Card';
8+
import { PageHead } from '../../../components/PageHead';
9+
import { ScrollList } from '../../../components/ScrollList';
10+
import issueStore, { IssueFilter, IssueModel } from '../../../models/Issue';
11+
import { i18n } from '../../../models/Translation';
12+
13+
const issueFilter: IssueFilter = {
14+
repository_url: 'https://github.com/idea2app',
15+
state: 'open',
16+
title: 'reward',
17+
};
18+
19+
export const getServerSideProps = compose(cache(), errorLogger, translator(i18n), async () => {
20+
const list = await new IssueModel().getList(issueFilter);
21+
22+
return { props: JSON.parse(JSON.stringify({ list })) };
23+
});
24+
25+
const IssuesPage: FC<{ list: Issue[] }> = observer(({ list }) => (
26+
<Grid container className="px-4 py-20">
27+
<PageHead title="GitHub-reward issues" />
28+
29+
<h1>GitHub-reward issues</h1>
30+
31+
<ScrollList
32+
translator={i18n}
33+
store={issueStore}
34+
filter={issueFilter}
35+
defaultData={list}
36+
renderList={allItems => (
37+
<Grid container spacing={2}>
38+
{allItems.map(issue => (
39+
<Grid key={issue.id} size={{ xs: 12, sm: 6, md: 4 }}>
40+
<IssueCard className="h-full" {...issue} />
41+
</Grid>
42+
))}
43+
</Grid>
44+
)}
45+
/>
46+
</Grid>
47+
));
48+
49+
export default IssuesPage;

0 commit comments

Comments
 (0)