Skip to content

Commit

Permalink
[add] Activity Forum detail page & Timeline component (#348)
Browse files Browse the repository at this point in the history
  • Loading branch information
TechQuery authored Oct 23, 2024
1 parent 17739b5 commit bca8589
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 169 deletions.
53 changes: 53 additions & 0 deletions components/Activity/Forum/Timeline.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.timeline {
position: relative;
margin: 0 auto;
max-width: 50%;
@media (max-width: 991px) {
max-width: 100%;
}
&::after {
position: absolute;
top: 0;
bottom: 0;
left: 10px;
margin-left: -3px;
background-color: #fff;
width: 3px;
content: '';
}
}
.timelineItem {
position: relative;
background-color: inherit;
width: 100%;

&::after {
position: absolute;
top: 18px;
right: 1px;
z-index: 1;
border-radius: 50%;
background-color: #fff;
width: 17px;
height: 17px;
content: '';
}
}
.right {
left: auto;
padding: 0px 0px 20px 40px;

&::before {
position: absolute;
top: 18px;
left: 30px;
z-index: 1;
border: medium solid #fff;
border-width: 10px 10px 10px 0;
border-color: transparent #fff transparent transparent;
content: ' ';
}
&::after {
left: 0;
}
}
54 changes: 54 additions & 0 deletions components/Activity/Forum/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { TableCellValue } from 'mobx-lark';
import { FC } from 'react';
import { Card } from 'react-bootstrap';

import { Agenda } from '../../../models/Activity/Agenda';
import { blobURLOf } from '../../../models/Base';
import { TimeRange } from '../../Base/TimeRange';
import { ActivityPeople } from '../People';
import styles from './Timeline.module.less';

export interface ForumTimelineProps {
activityId: TableCellValue;
agendas: Agenda[];
}

/**
* @see {@link https://mdbootstrap.com/docs/standard/extended/timeline/#section-timeline-gradient-bg}
*/
const ForumTimeline: FC<ForumTimelineProps> = ({ activityId, agendas }) => (
<div className={styles.timeline}>
{agendas.map(
({ id, title, startTime, endTime, summary, mentors, mentorAvatars }) => (
<div
key={id as string}
className={`position-relative ${styles.timelineItem} ${styles.right}`}
>
<Card>
<Card.Body className="p-4">
<h3 className="h5">
<a
className="text-decoration-none stretched-link"
href={`/activity/${activityId}/agenda/${id}`}
>
{title as string}
</a>
</h3>
<span className="small text-muted">
<TimeRange {...{ startTime, endTime }} />
</span>
<ActivityPeople
names={mentors as string[]}
avatars={(mentorAvatars as TableCellValue[])?.map(file =>
blobURLOf([file] as TableCellValue),
)}
/>
<p className="mt-2 mb-0">{summary as string}</p>
</Card.Body>
</Card>
</div>
),
)}
</div>
);
export default ForumTimeline;
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@giscus/react": "^3.0.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/mdx": "^15.0.0",
"@next/mdx": "^15.0.1",
"@sentry/nextjs": "^8.35.0",
"array-unique-proposal": "^0.3.4",
"classnames": "^2.5.1",
Expand All @@ -32,7 +32,7 @@
"mobx-react-helper": "^0.3.1",
"mobx-restful": "^1.0.1",
"mobx-restful-table": "^2.0.0",
"next": "^15.0.0",
"next": "^15.0.1",
"next-pwa": "^5.6.0",
"next-ssr-middleware": "^0.8.8",
"next-with-less": "^3.0.1",
Expand Down Expand Up @@ -61,17 +61,17 @@
"@types/eslint-config-prettier": "^6.11.3",
"@types/eslint__eslintrc": "^2.1.2",
"@types/eslint__js": "^8.42.3",
"@types/leaflet": "^1.9.13",
"@types/leaflet": "^1.9.14",
"@types/lodash": "^4.17.12",
"@types/mdx": "^2.0.13",
"@types/next-pwa": "^5.6.9",
"@types/node": "^20.16.14",
"@types/react": "^18.3.11",
"@types/node": "^20.17.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"eslint": "^9.13.0",
"eslint-config-next": "^15.0.0",
"eslint-config-next": "^15.0.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-simple-import-sort": "^12.1.1",
"get-git-folder": "^0.1.2",
"globals": "^15.11.0",
Expand Down
9 changes: 3 additions & 6 deletions pages/activity/[id]/agenda/[agendaId]/invitation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,13 @@ export default class InvitationPage extends Component<InvitationPageProps> {
{ sharedURL } = this;
const { name } = activity,
{ title, summary } = agenda;
const pageTitle = `${title} - ${name}`;

return (
<>
<PageHead title={`${title} - ${name}`} />
<PageHead title={pageTitle} />

<ShareBox
title={title as string}
text={summary as string}
url={sharedURL}
>
<ShareBox title={pageTitle} text={summary as string} url={sharedURL}>
{this.renderContent()}
</ShareBox>
</>
Expand Down
98 changes: 98 additions & 0 deletions pages/activity/[id]/forum/[forumId].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { ShareBox } from 'idea-react';
import { TableCellLocation } from 'mobx-lark';
import { observer } from 'mobx-react';
import { cache, compose, errorLogger, router } from 'next-ssr-middleware';
import { QRCodeSVG } from 'qrcode.react';
import { Component } from 'react';
import { Container } from 'react-bootstrap';

import ForumTimeline from '../../../../components/Activity/Forum/Timeline';
import { PageHead } from '../../../../components/Layout/PageHead';
import { Activity, ActivityModel } from '../../../../models/Activity';
import { Agenda } from '../../../../models/Activity/Agenda';
import { Forum } from '../../../../models/Activity/Forum';
import { API_Host } from '../../../../models/Base';
import { t } from '../../../../models/Base/Translation';
import { fileURLOf } from '../../../api/lark/file/[id]';

interface ForumPageProps {
activity: Activity;
forum: Forum;
agendas: Agenda[];
}

export const getServerSideProps = compose<
Record<'id' | 'forumId', string>,
ForumPageProps
>(cache(), router, errorLogger, async ({ params: { id, forumId } = {} }) => {
const activityStore = new ActivityModel();

const activity = await activityStore.getOne(id!);
const forum = await activityStore.currentForum!.getOne(forumId!);
const agendas = await activityStore.currentAgenda!.getAll({
forum: forum.name,
});

return {
props: JSON.parse(JSON.stringify({ activity, forum, agendas })),
};
});

@observer
export default class ForumPage extends Component<ForumPageProps> {
sharedURL = `${API_Host}/activity/${this.props.activity.id}/forum/${this.props.forum.id}`;

renderContent() {
const { activity, forum, agendas } = this.props,
{ sharedURL } = this;
const { id: activityId, name, city, location, image, cardImage } = activity,
{ name: forumName, location: room } = forum;

return (
<Container
className={`d-flex flex-column justify-content-around align-items-center text-center py-5`}
style={{
backgroundImage: `url(${fileURLOf(cardImage || image)})`,
backgroundSize: 'cover',
}}
>
<header className="d-flex flex-column align-items-center gap-4">
<h1>{name as string}</h1>
<h2>{forumName as string}</h2>

<ul className="list-unstyled d-flex flex-column align-items-center gap-4">
<li>🏙{city as string}</li>
<li>🗺{(location as TableCellLocation)?.full_address}</li>
<li>🚪{room as string}</li>
</ul>
</header>
<section className="p-3">
<ForumTimeline {...{ activityId, agendas }} />
</section>
<footer className="d-flex flex-column align-items-center gap-4">
<QRCodeSVG value={sharedURL} />

<div>{t('press_to_share')}</div>
</footer>
</Container>
);
}

render() {
const { activity, forum } = this.props,
{ sharedURL } = this;
const { name } = activity,
{ name: forumName, summary } = forum;
const pageTitle = `${forumName} - ${name}`;

return (
<>
<PageHead title={pageTitle} />

<ShareBox title={pageTitle} text={summary as string} url={sharedURL}>
{this.renderContent()}
</ShareBox>
</>
);
}
}
10 changes: 3 additions & 7 deletions pages/activity/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,20 +331,16 @@ export default class ActivityDetailPage extends Component<ActivityDetailPageProp
>
{t('financial_disclosure')}
</Button>
<Button
className="text-nowrap"
variant="success"
href="/search?keywords=收官"
>
{t('previous_activities')}
</Button>
<Button
className="text-nowrap"
variant="success"
href={`/activity/${activity.id}/charts`}
>
{t('activity_statistics')}
</Button>
<Button className="text-nowrap" href="/search/article?keywords=收官">
{t('previous_activities')}
</Button>
</Stack>

<Container>
Expand Down
Loading

1 comment on commit bca8589

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for kaiyuanshe ready!

✅ Preview
https://kaiyuanshe-2d2po0p3m-techquerys-projects.vercel.app

Built with commit bca8589.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.