Skip to content

Commit f792593

Browse files
CopilotTechQuery
andcommitted
Add team detail page with translations and routing
Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com>
1 parent 0fe73e3 commit f792593

4 files changed

Lines changed: 224 additions & 0 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { TableCellUser } from 'mobx-lark';
2+
import { observer } from 'mobx-react';
3+
import { cache, compose, errorLogger } from 'next-ssr-middleware';
4+
import { FC, useContext } from 'react';
5+
import { Badge, Card, Col, Container, Row, Tab, Tabs } from 'react-bootstrap';
6+
7+
import { LarkImage } from '../../../../components/LarkImage';
8+
import { PageHead } from '../../../../components/Layout/PageHead';
9+
import { Activity, ActivityModel } from '../../../../models/Activity';
10+
import { Person, PersonModel, Project, ProjectModel } from '../../../../models/Hackathon';
11+
import { I18nContext } from '../../../../models/Translation';
12+
13+
export const getServerSideProps = compose<{ id: string; tid: string }>(
14+
cache(),
15+
errorLogger,
16+
async ({ params }) => {
17+
const activityModel = new ActivityModel();
18+
const activity = await activityModel.getOne(params!.id);
19+
20+
// @ts-expect-error Upstream compatibility
21+
const { appId, tableIdMap } = activity.databaseSchema;
22+
23+
const projectModel = new ProjectModel(appId, tableIdMap.Project);
24+
const team = await projectModel.getOne(params!.tid);
25+
26+
const personModel = new PersonModel(appId, tableIdMap.Person);
27+
const allPeople = await personModel.getAll();
28+
29+
// Filter team members from all people based on the team's members field
30+
const teamMemberNames = (team.members as string[]) || [];
31+
const teamMembers = allPeople.filter((person) =>
32+
teamMemberNames.includes(person.name as string),
33+
);
34+
35+
return {
36+
props: {
37+
activity,
38+
team,
39+
teamMembers,
40+
},
41+
};
42+
},
43+
);
44+
45+
interface TeamPageProps {
46+
activity: Activity;
47+
team: Project;
48+
teamMembers: Person[];
49+
}
50+
51+
const TeamPage: FC<TeamPageProps> = observer(({ activity, team, teamMembers }) => {
52+
const { t } = useContext(I18nContext);
53+
54+
const { name: activityName } = activity;
55+
const { name: displayName, summary: description, createdBy, score } = team;
56+
57+
const currentRoute = [
58+
{ title: activityName as string, href: `/hackathon/${activityName}` },
59+
{ title: displayName as string },
60+
];
61+
62+
return (
63+
<Container as="main" className="mt-4">
64+
<PageHead title={`${displayName} - ${activityName}`} />
65+
66+
<nav aria-label="breadcrumb">
67+
<ol className="breadcrumb">
68+
{currentRoute.map(({ title, href }) =>
69+
href ? (
70+
<li key={title} className="breadcrumb-item">
71+
<a href={href}>{title}</a>
72+
</li>
73+
) : (
74+
<li key={title} className="breadcrumb-item active" aria-current="page">
75+
{title}
76+
</li>
77+
),
78+
)}
79+
</ol>
80+
</nav>
81+
82+
<Row className="mt-4">
83+
<Col xs={12} sm={4}>
84+
<Card style={{ minWidth: '15rem' }}>
85+
<Card.Header className="bg-white">
86+
<h1 className="h3 my-2">{displayName as string}</h1>
87+
<p className="text-muted">{description as string}</p>
88+
{score !== undefined && (
89+
<div className="text-center mt-3">
90+
<Badge bg="primary" className="fs-5">
91+
{t('score')}: {score as number}
92+
</Badge>
93+
</div>
94+
)}
95+
</Card.Header>
96+
<Card.Body>
97+
<h2 className="text-dark fw-bold h6 mb-3">
98+
👥 {t('team_members')}
99+
</h2>
100+
<ul className="list-unstyled">
101+
{teamMembers.map((member) => (
102+
<li key={member.id as string} className="mb-3">
103+
<div className="d-flex align-items-center">
104+
<LarkImage
105+
src={member.avatar}
106+
alt={member.name as string}
107+
className="rounded-circle me-2"
108+
style={{ width: '40px', height: '40px' }}
109+
/>
110+
<div>
111+
<div className="fw-bold">{member.name as string}</div>
112+
{member.githubLink && (
113+
<a
114+
href={member.githubLink as string}
115+
target="_blank"
116+
rel="noreferrer"
117+
className="text-muted small"
118+
>
119+
{t('view_github')}
120+
</a>
121+
)}
122+
</div>
123+
</div>
124+
</li>
125+
))}
126+
</ul>
127+
</Card.Body>
128+
</Card>
129+
</Col>
130+
<Col xs={12} sm={8}>
131+
<Tabs defaultActiveKey="update" id="team-detail-tabs">
132+
<Tab className="pt-2" eventKey="update" title={t('latest_news')}>
133+
<div className="h1 my-5 text-center text-muted">{t('no_news_yet')}</div>
134+
</Tab>
135+
<Tab eventKey="teamWork" title={t('team_works')} className="pt-2">
136+
<div className="mt-3">
137+
{team.products && (team.products as string[]).length > 0 ? (
138+
<ul className="list-unstyled">
139+
{(team.products as string[]).map((product, index) => (
140+
<li key={index} className="mb-2">
141+
<Card body>
142+
<h5>{product}</h5>
143+
</Card>
144+
</li>
145+
))}
146+
</ul>
147+
) : (
148+
<div className="text-center text-muted my-5">
149+
{t('no_news_yet')}
150+
</div>
151+
)}
152+
</div>
153+
</Tab>
154+
</Tabs>
155+
</Col>
156+
</Row>
157+
158+
{createdBy && (
159+
<Row className="mt-4">
160+
<Col>
161+
<Card>
162+
<Card.Body>
163+
<h3 className="h5">{t('created_by')}</h3>
164+
<div className="mt-3">
165+
<div className="fw-bold">{(createdBy as TableCellUser).name}</div>
166+
<a
167+
href={`mailto:${(createdBy as TableCellUser).email}`}
168+
className="text-muted"
169+
>
170+
{(createdBy as TableCellUser).email}
171+
</a>
172+
</div>
173+
</Card.Body>
174+
</Card>
175+
</Col>
176+
</Row>
177+
)}
178+
</Container>
179+
);
180+
});
181+
182+
export default TeamPage;

translation/en-US.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,18 @@ export default {
233233
male: 'Male',
234234
female: 'Female',
235235
other: 'Other',
236+
237+
// Team detail page
238+
team_members: 'Team Members',
239+
team_works: 'Team Works',
240+
latest_news: 'Latest News',
241+
no_news_yet: 'No news yet',
242+
join_team: 'Join Team',
243+
leave_team: 'Leave Team',
244+
manage_team: 'Manage Team',
245+
cancel_application: 'Cancel Application',
246+
please_make_sure: 'Please confirm',
247+
success: 'Success',
248+
team_description: 'Team Description',
249+
team_details: 'Team Details',
236250
};

translation/zh-CN.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,18 @@ export default {
229229
male: '男',
230230
female: '女',
231231
other: '其他',
232+
233+
// Team detail page
234+
team_members: '团队成员',
235+
team_works: '团队作品',
236+
latest_news: '最新动态',
237+
no_news_yet: '暂无动态',
238+
join_team: '加入团队',
239+
leave_team: '退出团队',
240+
manage_team: '管理团队',
241+
cancel_application: '取消申请',
242+
please_make_sure: '请确认',
243+
success: '成功',
244+
team_description: '团队描述',
245+
team_details: '团队详情',
232246
};

translation/zh-TW.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,18 @@ export default {
229229
male: '男',
230230
female: '女',
231231
other: '其他',
232+
233+
// Team detail page
234+
team_members: '團隊成員',
235+
team_works: '團隊作品',
236+
latest_news: '最新動態',
237+
no_news_yet: '暫無動態',
238+
join_team: '加入團隊',
239+
leave_team: '退出團隊',
240+
manage_team: '管理團隊',
241+
cancel_application: '取消申請',
242+
please_make_sure: '請確認',
243+
success: '成功',
244+
team_description: '團隊描述',
245+
team_details: '團隊詳情',
232246
};

0 commit comments

Comments
 (0)