Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"pflannery.vscode-versionlens",
"christian-kohler.npm-intellisense",
"esbenp.prettier-vscode",
"rangav.vscode-thunder-client",
"Postman.postman-for-vscode",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"github.vscode-github-actions",
Expand Down
12 changes: 10 additions & 2 deletions models/Hackathon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,16 @@ export class ProjectModel extends BiDataTable<Project>() {

queryOptions: BiDataQueryOptions = { text_field_as_array: false };

extractFields({ fields: { members, products, ...fields }, ...meta }: TableRecord<Project>) {
extractFields({
fields: { members, products, prize, ...fields },
...meta
}: TableRecord<Project>) {
return {
...meta,
...fields,
members: (members as TableCellRelation[])?.map(normalizeText),
products: (products as TableCellRelation[])?.map(normalizeText),
prize: (prize as TableCellRelation[])?.map(normalizeText),
Comment on lines +154 to +163

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 '\b(prize|ProjectModel|Project)\b' --glob '*.{ts,tsx}' .

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 22452


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files | rg '(^models/Hackathon\.ts$|^pages/hackathon/\[id\]/team/\[tid\]\.tsx$|^models/(BiDataTable|base).*\.ts$|^node_modules/mobx-lark|package\.json)'

echo
echo "== models/Hackathon.ts relevant sections =="
sed -n '1,220p' models/Hackathon.ts

echo
echo "== pages/hackathon/[id]/team/[tid].tsx relevant sections =="
sed -n '1,260p' 'pages/hackathon/[id]/team/[tid].tsx'

echo
echo "== search for extractFields/type overrides =="
rg -n "extractFields|BiDataTable|queryOptions|ProjectModel|export type Project" --glob '*.{ts,tsx}' models pages

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 19747


统一 prize 的模型输出和页面渲染契约。

models/Hackathon.ts 已将 ProjectModel.extracFieldsprize 输出为 string[],但团队详情页仍按单个奖项拼接和渲染,多奖项会丢失。更新 ProjectPagePropspages/hackathon/[id]/team/[tid].tsx,使奖项列表按 prize?: string[] 处理;若只保留单奖项字段,则移除模型的多奖项转换。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@models/Hackathon.ts` around lines 154 - 163, Update the ProjectPageProps
contract and the team detail page in pages/hackathon/[id]/team/[tid].tsx to
treat prize as an optional string array, preserving and rendering every prize
rather than concatenating a single value; keep ProjectModel.extractFields
consistent with this multi-prize output.

};
}
}
Expand All @@ -178,7 +182,11 @@ export class MemberModel extends BiDataTable<Member>() {
...fields,
person: (person as TableCellUser[])?.[0],
summary: (summary as TableCellText[])!.map(normalizeText),
skills: skills?.toString().split(/\s*,\s*/) || [],
skills:
(skills as TableCellText[])!
.map(normalizeText)
.join()
.split(/\s*,\s*/) || [],
Comment thread
TechQuery marked this conversation as resolved.
Comment thread
TechQuery marked this conversation as resolved.
githubAccount: (githubAccount as TableCellText[])!.map(normalizeText),
};
}
Expand Down
7 changes: 4 additions & 3 deletions models/Translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ export interface I18nProps {
export type I18nKey = keyof typeof zhCN;

export const createI18nStore = <N extends LanguageCode, K extends string>(
language?: N,
language = 'zh-CN' as N,
serializedData?: string,
) => {
const data = serializedData && (JSON.parse(serializedData, decodeFunctions) as TranslationMap<K>);

const data = serializedData
? (JSON.parse(serializedData, decodeFunctions) as TranslationMap<K>)
: zhCN;
const store = new TranslationModel({
...i18nData,
...(language && { [language]: data }),
Expand Down
5 changes: 0 additions & 5 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ const rewrites: NextConfig['rewrites'] = async () => ({

const redirects: NextConfig['redirects'] = async () =>
['/wiki', '/recipe', '/policy'].flatMap(route => [
{
source: route,
destination: `https://wiki.fcc-cd.dev${route}`,
permanent: true,
},
{
source: `${route}/:path*`,
destination: `https://wiki.fcc-cd.dev${route}/:path*`,
Expand Down
23 changes: 11 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@koa/router": "^15.7.0",
"@mdx-js/loader": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@next/mdx": "16.2.11",
"@next/mdx": "16.2.12",
"core-js": "^3.49.0",
"echarts-jsx": "^0.6.0",
"file-type": "^22.0.1",
Expand All @@ -36,9 +36,9 @@
"mobx-react": "^9.2.2",
"mobx-react-helper": "^0.5.1",
"mobx-restful": "^2.1.4",
"mobx-restful-table": "^2.6.3",
"mobx-restful-table": "^2.6.6",
"mobx-strapi": "^0.8.1",
"next": "16.2.11",
"next": "16.2.12",
"next-pwa": "^5.6.0",
"next-ssr-middleware": "^1.1.1",
"nodemailer": "^9.0.3",
Expand All @@ -55,7 +55,7 @@
"devDependencies": {
"@cspell/eslint-plugin": "^10.0.1",
"@eslint/js": "^10.0.1",
"@next/eslint-plugin-next": "16.2.11",
"@next/eslint-plugin-next": "16.2.12",
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
"@stylistic/eslint-plugin": "^5.10.0",
"@types/eslint-config-prettier": "^6.11.3",
Expand All @@ -64,24 +64,23 @@
"@types/next-pwa": "^5.6.9",
"@types/node": "^24.13.3",
"@types/nodemailer": "^8.0.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"cross-env": "^10.1.0",
"eslint": "^10.7.0",
"eslint-config-next": "16.2.11",
"eslint": "^10.8.0",
"eslint-config-next": "16.2.12",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^14.0.0",
"globals": "^17.7.0",
"globals": "^17.8.0",
"husky": "^9.1.7",
"jiti": "^2.7.0",
"less": "^4.8.0",
"less": "^4.8.1",
"less-loader": "^13.0.0",
"lint-staged": "^17.2.0",
"lint-staged": "^17.3.0",
"next-with-less": "^3.0.1",
"prettier": "^3.9.6",
"prettier-plugin-css-order": "^2.2.0",
"sass": "^1.101.7",
"typescript": "~5.9.3",
"typescript-eslint": "^8.65.0"
},
Expand Down
39 changes: 12 additions & 27 deletions pages/NGO/[year]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { observer } from 'mobx-react';
import dynamic from 'next/dynamic';
import { GetStaticPaths } from 'next';
import { GetStaticProps } from 'next';
import { FC, useContext } from 'react';
import { Button, Container } from 'react-bootstrap';

import { PageHead } from '../../../components/Layout/PageHead';
import { CityStatisticMap } from '../../../components/Map/CityStatisticMap';
import { SearchBar } from '../../../components/Navigator/SearchBar';
import {
OrganizationModel,
OrganizationStatistic,
OrganizationYearStatisticModel,
} from '../../../models/Organization';
import { OrganizationModel, OrganizationStatistic } from '../../../models/Organization';
import { I18nContext } from '../../../models/Translation';
import { skipBuilding } from '../../api/SSG';
import { skipBuildingAll } from '../../api/SSG';

const OrganizationCharts = dynamic(() => import('../../../components/Organization/Charts'), {
ssr: false,
Expand All @@ -24,30 +20,19 @@ interface OrganizationPageProps {
statistic: OrganizationStatistic;
}

export const getStaticPaths: GetStaticPaths<{ year: string }> = async () => {
const yearStore = new OrganizationYearStatisticModel();
export const getStaticPaths = skipBuildingAll;

const years = await yearStore.getAll();
export const getStaticProps: GetStaticProps<OrganizationPageProps, { year: string }> = async ({
params,
}) => {
const { year } = params!;

return {
paths: years.map(({ name }) => name && { params: { year: name! } }).filter(Boolean) as {
params: { year: string };
}[],
fallback: 'blocking',
};
};

export const getStaticProps = skipBuilding<OrganizationPageProps, { year: string }>(
async ({ params }) => {
const { year } = params!;
const organizationStore = new OrganizationModel();

const organizationStore = new OrganizationModel();
const statistic = await organizationStore.getStatistic({ startYear: year });

const statistic = await organizationStore.getStatistic({ startYear: year });

return { props: { year, statistic } };
},
);
return { props: { year, statistic } };
};
Comment thread
TechQuery marked this conversation as resolved.

const OrganizationPage: FC<OrganizationPageProps> = observer(({ year, statistic }) => {
const { t } = useContext(I18nContext);
Expand Down
36 changes: 13 additions & 23 deletions pages/NGO/[year]/landscape.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { observer } from 'mobx-react';
import { GetStaticPaths } from 'next';
import { GetStaticProps } from 'next';
import { FC, useContext } from 'react';
import { Container } from 'react-bootstrap';

Expand All @@ -8,34 +8,24 @@ import {
OpenCollaborationLandscape,
OpenCollaborationLandscapeProps,
} from '../../../components/Organization/Landscape';
import { OrganizationModel, OrganizationYearStatisticModel } from '../../../models/Organization';
import { OrganizationModel } from '../../../models/Organization';
import { I18nContext } from '../../../models/Translation';
import { skipBuilding } from '../../api/SSG';
import { skipBuildingAll } from '../../api/SSG';

export const getStaticPaths: GetStaticPaths<{ year: string }> = async () => {
const yearStore = new OrganizationYearStatisticModel();
export const getStaticPaths = skipBuildingAll;

const years = await yearStore.getAll();
export const getStaticProps: GetStaticProps<
Pick<OrganizationModel, 'typeMap'>,
{ year: string }
> = async ({ params }) => {
const { year } = params!;

return {
paths: years.map(({ name }) => name && { params: { year: name! } }).filter(Boolean) as {
params: { year: string };
}[],
fallback: 'blocking',
};
};

export const getStaticProps = skipBuilding<Pick<OrganizationModel, 'typeMap'>, { year: string }>(
async ({ params }) => {
const { year } = params!;
const organizationStore = new OrganizationModel();

const organizationStore = new OrganizationModel();
const typeMap = await organizationStore.groupAllByType({ startYear: year });

const typeMap = await organizationStore.groupAllByType({ startYear: year });

return { props: JSON.parse(JSON.stringify({ typeMap })) };
},
);
return { props: JSON.parse(JSON.stringify({ typeMap })) };
};
Comment thread
TechQuery marked this conversation as resolved.

const LandscapePage: FC<OpenCollaborationLandscapeProps> = observer(props => {
const { t } = useContext(I18nContext);
Expand Down
4 changes: 3 additions & 1 deletion pages/api/SSG.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DataObject } from 'mobx-restful';
import { GetStaticProps, GetStaticPropsResult } from 'next';
import { GetStaticPaths, GetStaticProps, GetStaticPropsResult } from 'next';
import { ParsedUrlQuery } from 'querystring';
import { Minute, Second } from 'web-utility';

Expand All @@ -23,3 +23,5 @@ export const skipBuilding =
return fallback;
}
};

export const skipBuildingAll: GetStaticPaths = async () => ({ paths: [], fallback: 'blocking' });
14 changes: 10 additions & 4 deletions pages/award/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { cache, compose, errorLogger } from 'next-ssr-middleware';
import { GetStaticProps } from 'next';
import { FC } from 'react';

import { Award, AwardModel } from '../../models/Award';
import { lark } from '../api/Lark/core';
Comment on lines +1 to +5

export const getServerSideProps = compose(cache(), errorLogger, async () => {
const awards = await new AwardModel().getAll();
export const getStaticProps: GetStaticProps<{ awards: Award[] }> = async () => {
await lark.getAccessToken();

const store = new AwardModel();
store.client = lark.client;

const awards = await store.getAll();

return { props: { awards } };
});
};

const AwardPage: FC<{ awards: Award[] }> = ({ awards }) => {
return <></>;
Expand Down
44 changes: 33 additions & 11 deletions pages/hackathon/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { TableCellLocation, TableCellValue, TableFormView } from 'mobx-lark';
import { observer } from 'mobx-react';
import { cache, compose, errorLogger } from 'next-ssr-middleware';
import { TimeUnit } from 'idea-react';
import { FC, ReactNode, useContext } from 'react';
import { Minute, Second } from 'web-utility';

import {
HackathonActionHub,
Expand Down Expand Up @@ -61,6 +61,8 @@ import {
TemplateModel,
} from '../../models/Hackathon';
import { I18nContext } from '../../models/Translation';
import { lark } from '../api/Lark/core';
import { skipBuilding, skipBuildingAll } from '../api/SSG';

interface HackathonDetailProps {
activity: Activity;
Expand All @@ -74,31 +76,51 @@ interface HackathonDetailProps {
};
}

export const getServerSideProps = compose<{ id: string }>(
cache(),
errorLogger,
export const getStaticPaths = skipBuildingAll;

export const getStaticProps = skipBuilding<HackathonDetailProps, { id: string }>(
async ({ params }) => {
const activity = await new ActivityModel().getOne(params!.id);
await lark.getAccessToken();

const activityStore = new ActivityModel();
activityStore.client = lark.client;

const activity = await activityStore.getOne(params!.id);
const { appId, tableIdMap } = activity.databaseSchema || {};

if (!appId || !tableIdMap) return { notFound: true };

for (const key of RequiredTableKeys) if (!tableIdMap[key]) return { notFound: true };

const personStore = new PersonModel(appId, tableIdMap.Person);
const organizationStore = new OrganizationModel(appId, tableIdMap.Organization);
const agendaStore = new AgendaModel(appId, tableIdMap.Agenda);
const prizeStore = new PrizeModel(appId, tableIdMap.Prize);
const templateStore = new TemplateModel(appId, tableIdMap.Template);
const projectStore = new ProjectModel(appId, tableIdMap.Project);
personStore.client =
organizationStore.client =
agendaStore.client =
prizeStore.client =
templateStore.client =
projectStore.client =
lark.client;

const [people, organizations, agenda, prizes, templates, projects] = await Promise.all([
new PersonModel(appId, tableIdMap.Person).getAll(),
new OrganizationModel(appId, tableIdMap.Organization).getAll(),
new AgendaModel(appId, tableIdMap.Agenda).getAll(),
new PrizeModel(appId, tableIdMap.Prize).getAll(),
new TemplateModel(appId, tableIdMap.Template).getAll(),
new ProjectModel(appId, tableIdMap.Project).getAll(),
personStore.getAll(),
organizationStore.getAll(),
agendaStore.getAll(),
prizeStore.getAll(),
templateStore.getAll(),
projectStore.getAll(),
]);

return {
props: {
activity,
hackathon: { people, organizations, agenda, prizes, templates, projects },
},
revalidate: +new Date(activity.endTime as number) < Date.now() ? undefined : Minute / Second,
};
},
);
Expand Down
Loading