Skip to content

Commit 82d3019

Browse files
committed
[add] Card component & List page of GitHub projects
[optimize] update Upstream packages
1 parent 8a08386 commit 82d3019

10 files changed

Lines changed: 830 additions & 646 deletions

File tree

components/Git/Card.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { text2color } from 'idea-react';
2+
import { GitRepository } from 'mobx-github';
3+
import { observer } from 'mobx-react';
4+
import { FC, useContext } from 'react';
5+
import { Badge, Button, Card, Col, Row } from 'react-bootstrap';
6+
7+
import { I18nContext } from '../../models/Translation';
8+
import { GitLogo } from './Logo';
9+
10+
export interface GitCardProps
11+
extends Pick<GitRepository, 'full_name' | 'html_url' | 'languages'>,
12+
Partial<Pick<GitRepository, 'topics' | 'description' | 'homepage'>> {
13+
className?: string;
14+
}
15+
16+
export const GitCard: FC<GitCardProps> = observer(
17+
({
18+
className = 'shadow-sm',
19+
full_name,
20+
html_url,
21+
languages = [],
22+
topics = [],
23+
description,
24+
homepage,
25+
}) => {
26+
const { t } = useContext(I18nContext);
27+
28+
return (
29+
<Card className={className}>
30+
<Card.Body className="d-flex flex-column gap-3">
31+
<Card.Title as="h3" className="h5">
32+
<a target="_blank" href={html_url} rel="noreferrer">
33+
{full_name}
34+
</a>
35+
</Card.Title>
36+
37+
<nav className="flex-fill">
38+
{topics.map(topic => (
39+
<Badge
40+
key={topic}
41+
className="me-1"
42+
bg={text2color(topic, ['light'])}
43+
as="a"
44+
target="_blank"
45+
href={`https://github.com/topics/${topic}`}
46+
>
47+
{topic}
48+
</Badge>
49+
))}
50+
</nav>
51+
<Row as="ul" className="list-unstyled g-4" xs={4}>
52+
{languages.map(language => (
53+
<Col key={language} as="li">
54+
<GitLogo name={language} />
55+
</Col>
56+
))}
57+
</Row>
58+
<Card.Text>{description}</Card.Text>
59+
</Card.Body>
60+
<Card.Footer className="d-flex justify-content-between align-items-center">
61+
{homepage && (
62+
<Button variant="success" target="_blank" href={homepage}>
63+
{t('home_page')}
64+
</Button>
65+
)}
66+
</Card.Footer>
67+
</Card>
68+
);
69+
},
70+
);

components/Git/Logo.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { observable } from 'mobx';
2+
import { observer } from 'mobx-react';
3+
import { PureComponent } from 'react';
4+
import { Image } from 'react-bootstrap';
5+
6+
export interface GitLogoProps {
7+
name: string;
8+
}
9+
10+
@observer
11+
export class GitLogo extends PureComponent<GitLogoProps> {
12+
@observable
13+
accessor path = '';
14+
15+
async componentDidMount() {
16+
const { name } = this.props;
17+
const topic = name.toLowerCase();
18+
19+
try {
20+
const { src } = await this.loadImage(
21+
`https://raw.githubusercontent.com/github/explore/master/topics/${topic}/${topic}.png`,
22+
);
23+
this.path = src;
24+
} catch {
25+
const { src } = await this.loadImage(`https://github.com/${name}.png`);
26+
27+
this.path = src;
28+
}
29+
}
30+
31+
loadImage(path: string) {
32+
return new Promise<HTMLImageElement>((resolve, reject) => {
33+
const image = new globalThis.Image();
34+
35+
image.onload = () => resolve(image);
36+
image.onerror = reject;
37+
38+
image.src = path;
39+
});
40+
}
41+
42+
render() {
43+
const { path } = this;
44+
const { name } = this.props;
45+
46+
return path && <Image fluid src={path} alt={name} />;
47+
}
48+
}

components/Navigator/MainNavigator.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
1919
href: '/article/open-collaborator-award',
2020
name: t('open_collaborator_award'),
2121
},
22+
{ href: '/project', name: t('open_source_projects') },
2223
{ href: '/issue', name: 'GitHub issues' },
2324
{
2425
href: 'https://github.com/Open-Source-Bazaar/Git-Hackathon-scaffold',

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,18 @@
4444
"@babel/plugin-proposal-decorators": "^7.28.0",
4545
"@babel/plugin-transform-typescript": "^7.28.0",
4646
"@babel/preset-react": "^7.27.1",
47-
"@cspell/eslint-plugin": "^9.1.3",
48-
"@eslint/js": "^9.30.1",
47+
"@cspell/eslint-plugin": "^9.1.5",
48+
"@eslint/js": "^9.31.0",
4949
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
5050
"@stylistic/eslint-plugin": "^5.1.0",
5151
"@types/eslint-config-prettier": "^6.11.3",
5252
"@types/koa": "^2.15.0",
5353
"@types/koa__router": "^12.0.4",
5454
"@types/next-pwa": "^5.6.9",
55-
"@types/node": "^22.16.0",
55+
"@types/node": "^22.16.3",
5656
"@types/react": "^19.1.8",
5757
"@types/react-dom": "^19.1.6",
58-
"eslint": "^9.30.1",
58+
"eslint": "^9.31.0",
5959
"eslint-config-next": "^15.3.5",
6060
"eslint-config-prettier": "^10.1.5",
6161
"eslint-plugin-react": "^7.37.5",

pages/project.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Loading } from 'idea-react';
2+
import { GitRepository, RepositoryModel } from 'mobx-github';
3+
import { observer } from 'mobx-react';
4+
import { ScrollList } from 'mobx-restful-table';
5+
import { cache, compose, errorLogger } from 'next-ssr-middleware';
6+
import { FC, useContext } from 'react';
7+
import { Col, Container, Row } from 'react-bootstrap';
8+
9+
import { GitCard } from '../components/Git/Card';
10+
import { PageHead } from '../components/Layout/PageHead';
11+
import { repositoryStore } from '../models/Repository';
12+
import { I18nContext } from '../models/Translation';
13+
14+
export const getServerSideProps = compose(cache(), errorLogger, async () => {
15+
const list = await new RepositoryModel('Open-Source-Bazaar').getList({
16+
relation: ['languages'],
17+
});
18+
19+
return { props: JSON.parse(JSON.stringify({ list })) };
20+
});
21+
22+
const ProjectListPage: FC<{ list: GitRepository[] }> = observer(({ list }) => {
23+
const i18n = useContext(I18nContext);
24+
const { t } = i18n;
25+
26+
return (
27+
<Container>
28+
<PageHead title={t('open_source_projects')} />
29+
<h1 className="my-4">{t('open_source_projects')}</h1>
30+
31+
{repositoryStore.downloading > 0 && <Loading />}
32+
33+
<ScrollList
34+
translator={i18n}
35+
store={repositoryStore}
36+
filter={{ relation: ['languages'] }}
37+
renderList={allItems => (
38+
<Row as="ul" className="list-unstyled g-4" xs={1} sm={2}>
39+
{allItems.map(
40+
item =>
41+
item.homepage && (
42+
<Col key={item.id} as="li">
43+
<GitCard className="h-100 shadow-sm" {...item} />
44+
</Col>
45+
),
46+
)}
47+
</Row>
48+
)}
49+
defaultData={list}
50+
/>
51+
</Container>
52+
);
53+
});
54+
55+
export default ProjectListPage;

0 commit comments

Comments
 (0)