Skip to content

Commit 2d1d27d

Browse files
committed
feat: Refactor homepage filters with dedicated components and improved URL synchronization, update content, and dependencies.
1 parent 3ceb871 commit 2d1d27d

File tree

11 files changed

+839
-733
lines changed

11 files changed

+839
-733
lines changed

package-lock.json

Lines changed: 133 additions & 100 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@
3939
"country-list": "^2.4.1",
4040
"debug": "^4.3.7",
4141
"framer-motion": "^11.11.1",
42-
"glob": "^11.0.0",
42+
"glob": "^11.1.0",
4343
"next": "^14.2.5",
4444
"node-fetch": "^2.6.7",
45-
"puppeteer": "^24.4.0",
45+
"prop-types": "^15.8.1",
46+
"puppeteer": "^24.15.0",
4647
"react": "^18.2.0",
4748
"react-country-flag": "^3.1.0",
4849
"react-dom": "^18.2.0",

public/index.html.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,4 +417,3 @@ This page provides a filterable list of all resources. You can also download the
417417
*Compensation:* Donation
418418

419419
---
420-

public/yourselftoscience.pdf

-828 Bytes
Binary file not shown.

scripts/generateMarkdown.js

Lines changed: 133 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import fs from 'fs';
2-
import path from 'path';
3-
import { fileURLToPath } from 'url';
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
44
import { resources } from '../src/data/resources.js';
55
import { EU_COUNTRIES } from '../src/data/constants.js';
66

@@ -17,136 +17,136 @@ function generateStatsMarkdown() {
1717

1818
const resourcesByCountry = resources.reduce((acc, resource) => {
1919
if (resource.countries && resource.countries.length > 0) {
20-
resource.countries.forEach(country => {
20+
for (const country of resource.countries) {
2121
const countryName = country === 'European Union' || EU_COUNTRIES.includes(country) ? 'European Union' : country;
2222
acc[countryName] = (acc[countryName] || 0) + 1;
23-
});
23+
}
2424
} else {
2525
acc['Worldwide'] = (acc['Worldwide'] || 0) + 1;
2626
}
2727
return acc;
2828
}, {});
2929

3030
const resourcesByDataType = resources.reduce((acc, resource) => {
31-
(resource.dataTypes || []).forEach(type => {
32-
const baseType = type.startsWith('Wearable data') ? 'Wearable data' : type;
33-
acc[baseType] = (acc[baseType] || 0) + 1;
34-
});
35-
return acc;
31+
for (const type of (resource.dataTypes || [])) {
32+
const baseType = type.startsWith('Wearable data') ? 'Wearable data' : type;
33+
acc[baseType] = (acc[baseType] || 0) + 1;
34+
}
35+
return acc;
3636
}, {});
3737

3838
const resourcesByCompensation = resources.reduce((acc, resource) => {
39-
const type = resource.compensationType || 'donation';
40-
acc[type] = (acc[type] || 0) + 1;
41-
return acc;
39+
const type = resource.compensationType || 'donation';
40+
acc[type] = (acc[type] || 0) + 1;
41+
return acc;
4242
}, {});
4343

4444
const resourcesByEntityType = resources.reduce((acc, resource) => {
45-
const category = resource.entityCategory || 'Other';
46-
const subType = resource.entitySubType || 'Not Specified';
45+
const category = resource.entityCategory || 'Other';
46+
const subType = resource.entitySubType || 'Not Specified';
4747

48-
if (!acc[category]) {
49-
acc[category] = { count: 0, subTypes: {} };
50-
}
51-
acc[category].count++;
52-
acc[category].subTypes[subType] = (acc[category].subTypes[subType] || 0) + 1;
53-
return acc;
48+
if (!acc[category]) {
49+
acc[category] = { count: 0, subTypes: {} };
50+
}
51+
acc[category].count++;
52+
acc[category].subTypes[subType] = (acc[category].subTypes[subType] || 0) + 1;
53+
return acc;
5454
}, {});
55-
55+
5656
const topCountries = Object.entries(resourcesByCountry)
57-
.sort(([, a], [, b]) => b - a)
58-
.slice(0, 10);
57+
.sort(([, a], [, b]) => b - a)
58+
.slice(0, 10);
5959

6060
const dataTypesDistribution = Object.entries(resourcesByDataType)
61-
.sort(([, a], [, b]) => b - a);
61+
.sort(([, a], [, b]) => b - a);
6262

6363
const compensationDistribution = Object.entries(resourcesByCompensation)
64-
.sort(([, a], [, b]) => b - a);
65-
64+
.sort(([, a], [, b]) => b - a);
65+
6666
const entityTypeDistribution = Object.entries(resourcesByEntityType)
67-
.map(([name, data]) => ({
68-
name,
69-
count: data.count,
70-
subTypes: Object.entries(data.subTypes).sort(([, a], [, b]) => b - a),
71-
}))
72-
.sort((a, b) => b.count - a.count);
67+
.map(([name, data]) => ({
68+
name,
69+
count: data.count,
70+
subTypes: Object.entries(data.subTypes).sort(([, a], [, b]) => b - a),
71+
}))
72+
.sort((a, b) => b.count - a.count);
7373

7474
const euBreakdownStats = Object.entries(resources.reduce((acc, resource) => {
75-
if (resource.countries) {
76-
resource.countries.forEach(country => {
77-
if (country === 'European Union') {
78-
acc['EU-Wide'] = (acc['EU-Wide'] || 0) + 1;
79-
} else if (EU_COUNTRIES.includes(country)) {
80-
acc[country] = (acc[country] || 0) + 1;
81-
}
82-
});
75+
if (resource.countries) {
76+
for (const country of resource.countries) {
77+
if (country === 'European Union') {
78+
acc['EU-Wide'] = (acc['EU-Wide'] || 0) + 1;
79+
} else if (EU_COUNTRIES.includes(country)) {
80+
acc[country] = (acc[country] || 0) + 1;
81+
}
8382
}
84-
return acc;
83+
}
84+
return acc;
8585
}, {})).sort(([, a], [, b]) => b - a);
8686

8787

8888
// --- Generate Markdown Content ---
8989

9090
let mdContent = `# Project Statistics\n\n`;
9191
mdContent += `An overview of the resources available on Yourself to Science, providing insights into the landscape of citizen science contribution.\n\n`;
92-
92+
9393
mdContent += `## Overview\n\n`;
9494
mdContent += `- **Total Resources:** ${totalResources}\n\n`;
9595

9696
mdContent += `## Compensation Types\n\n`;
97-
compensationDistribution.forEach(([type, count]) => {
97+
for (const [type, count] of compensationDistribution) {
9898
mdContent += `- **${type.charAt(0).toUpperCase() + type.slice(1)}:** ${count}\n`;
99-
});
99+
}
100100
mdContent += `\n`;
101101

102102
mdContent += `## Top 10 Service Availability by Country\n\n`;
103-
topCountries.forEach(([country, count]) => {
103+
for (const [country, count] of topCountries) {
104104
mdContent += `- **${country}:** ${count}\n`;
105105
if (country === 'European Union') {
106106
mdContent += ` - **Breakdown:**\n`;
107-
euBreakdownStats.forEach(([euCountry, euCount]) => {
107+
for (const [euCountry, euCount] of euBreakdownStats) {
108108
mdContent += ` - **${euCountry}:** ${euCount}\n`;
109-
});
109+
}
110110
}
111-
});
111+
}
112112
mdContent += `\n`;
113113

114114
mdContent += `## Data Type Distribution\n\n`;
115-
dataTypesDistribution.forEach(([type, count]) => {
115+
for (const [type, count] of dataTypesDistribution) {
116116
mdContent += `- **${type}:** ${count}\n`;
117-
});
117+
}
118118
mdContent += `\n`;
119-
119+
120120
mdContent += `## Entity Type Distribution\n\n`;
121-
entityTypeDistribution.forEach(entity => {
122-
mdContent += `- **${entity.name}:** ${entity.count}\n`;
123-
if (entity.subTypes.length > 1) {
124-
entity.subTypes.forEach(([subType, subCount]) => {
125-
mdContent += ` - **${subType}:** ${subCount}\n`;
126-
});
121+
for (const entity of entityTypeDistribution) {
122+
mdContent += `- **${entity.name}:** ${entity.count}\n`;
123+
if (entity.subTypes.length > 1) {
124+
for (const [subType, subCount] of entity.subTypes) {
125+
mdContent += ` - **${subType}:** ${subCount}\n`;
127126
}
128-
});
127+
}
128+
}
129129
mdContent += `\n`;
130130

131131
mdContent += `## Live Data Access\n\n`;
132132
mdContent += `Use these persistent URLs for automated access to always get the latest version of the dataset.\n\n`;
133133
mdContent += `- **CSV Endpoint:** https://yourselftoscience.org/resources.csv\n`;
134134
mdContent += `- **JSON Endpoint:** https://yourselftoscience.org/resources.json\n`;
135-
135+
136136
const outputPath = path.join(__dirname, '../public/stats.md');
137137
fs.writeFileSync(outputPath, mdContent);
138138
console.log(`Successfully generated ${outputPath}`);
139139
}
140140

141141
function generateHomepageMarkdown() {
142142
console.log('Generating index.html.md...');
143-
143+
144144
let mdContent = `# Yourself to Science: Contribute to Science\n\n`;
145-
mdContent += `YourselfToScience.org is an open-source website providing a comprehensive list of services that allow individuals to contribute to scientific research with their data, genome, body, and more.\n\n`;
145+
mdContent += `Yourself to Science™ is an open-source project providing a comprehensive list of services that allow individuals to contribute to scientific research with their data, genome, body, and more.\n\n`;
146146
mdContent += `This page provides a filterable list of all resources. You can also download the full dataset as CSV or JSON.\n\n`;
147147
mdContent += `## All Resources\n\n`;
148148

149-
resources.forEach(resource => {
149+
for (const [index, resource] of resources.entries()) {
150150
mdContent += `### [${resource.title}](https://yourselftoscience.org/resource/${resource.slug})\n\n`;
151151
mdContent += `*Description:* ${resource.description}\n\n`;
152152
if (resource.dataTypes) {
@@ -158,72 +158,80 @@ function generateHomepageMarkdown() {
158158
if (resource.compensationType) {
159159
mdContent += `*Compensation:* ${resource.compensationType.charAt(0).toUpperCase() + resource.compensationType.slice(1)}\n\n`;
160160
}
161-
mdContent += `---\n\n`;
162-
});
161+
// Add separator only if it's not the last item
162+
if (index < resources.length - 1) {
163+
mdContent += `---\n\n`;
164+
} else {
165+
mdContent += `---`; // End with separator but no extra newlines
166+
}
167+
}
168+
169+
// Ensure single trailing newline
170+
mdContent += '\n';
163171

164172
const outputPath = path.join(__dirname, '../public/index.html.md');
165173
fs.writeFileSync(outputPath, mdContent);
166174
console.log(`Successfully generated ${outputPath}`);
167175
}
168176

169177
function generateClinicalTrialsMarkdown() {
170-
console.log('Generating clinical-trials.md...');
171-
const clinicalTrialResources = resources.filter(r => r.dataTypes.includes('Clinical trials'));
172-
173-
let mdContent = `# Clinical Trials\n\n`;
174-
mdContent += `This page lists resources related to contributing to clinical trials.\n\n`;
175-
mdContent += `## Clinical Trial Resources\n\n`;
176-
177-
clinicalTrialResources.forEach(resource => {
178-
mdContent += `### [${resource.title}](https://yourselftoscience.org/resource/${resource.slug})\n\n`;
179-
mdContent += `*Description:* ${resource.description}\n\n`;
180-
if (resource.dataTypes) {
181-
mdContent += `*Data Types:* ${resource.dataTypes.join(', ')}\n\n`;
182-
}
183-
if (resource.countries) {
184-
mdContent += `*Countries:* ${resource.countries.join(', ')}\n\n`;
185-
}
186-
if (resource.compensationType) {
187-
mdContent += `*Compensation:* ${resource.compensationType.charAt(0).toUpperCase() + resource.compensationType.slice(1)}\n\n`;
188-
}
189-
mdContent += `---\n\n`;
190-
});
178+
console.log('Generating clinical-trials.md...');
179+
const clinicalTrialResources = resources.filter(r => r.dataTypes.includes('Clinical trials'));
180+
181+
let mdContent = `# Clinical Trials\n\n`;
182+
mdContent += `This page lists resources related to contributing to clinical trials.\n\n`;
183+
mdContent += `## Clinical Trial Resources\n\n`;
184+
185+
for (const resource of clinicalTrialResources) {
186+
mdContent += `### [${resource.title}](https://yourselftoscience.org/resource/${resource.slug})\n\n`;
187+
mdContent += `*Description:* ${resource.description}\n\n`;
188+
if (resource.dataTypes) {
189+
mdContent += `*Data Types:* ${resource.dataTypes.join(', ')}\n\n`;
190+
}
191+
if (resource.countries) {
192+
mdContent += `*Countries:* ${resource.countries.join(', ')}\n\n`;
193+
}
194+
if (resource.compensationType) {
195+
mdContent += `*Compensation:* ${resource.compensationType.charAt(0).toUpperCase() + resource.compensationType.slice(1)}\n\n`;
196+
}
197+
mdContent += `---\n\n`;
198+
}
191199

192-
const outputPath = path.join(__dirname, '../public/clinical-trials.md');
193-
fs.writeFileSync(outputPath, mdContent);
194-
console.log(`Successfully generated ${outputPath}`);
200+
const outputPath = path.join(__dirname, '../public/clinical-trials.md');
201+
fs.writeFileSync(outputPath, mdContent);
202+
console.log(`Successfully generated ${outputPath}`);
195203
}
196204

197205
function generateOrganBodyTissueDonationMarkdown() {
198-
console.log('Generating organ-body-tissue-donation.md...');
199-
const donationResources = resources.filter(r =>
200-
r.dataTypes.includes('Organ') ||
201-
r.dataTypes.includes('Body') ||
202-
r.dataTypes.includes('Tissue')
203-
);
204-
205-
let mdContent = `# Organ, Body & Tissue Donation\n\n`;
206-
mdContent += `This page lists resources related to donating organs, bodies, or tissues for scientific research.\n\n`;
207-
mdContent += `## Organ, Body & Tissue Donation Resources\n\n`;
208-
209-
donationResources.forEach(resource => {
210-
mdContent += `### [${resource.title}](https://yourselftoscience.org/resource/${resource.slug})\n\n`;
211-
mdContent += `*Description:* ${resource.description}\n\n`;
212-
if (resource.dataTypes) {
213-
mdContent += `*Data Types:* ${resource.dataTypes.join(', ')}\n\n`;
214-
}
215-
if (resource.countries) {
216-
mdContent += `*Countries:* ${resource.countries.join(', ')}\n\n`;
217-
}
218-
if (resource.compensationType) {
219-
mdContent += `*Compensation:* ${resource.compensationType.charAt(0).toUpperCase() + resource.compensationType.slice(1)}\n\n`;
220-
}
221-
mdContent += `---\n\n`;
222-
});
206+
console.log('Generating organ-body-tissue-donation.md...');
207+
const donationResources = resources.filter(r =>
208+
r.dataTypes.includes('Organ') ||
209+
r.dataTypes.includes('Body') ||
210+
r.dataTypes.includes('Tissue')
211+
);
212+
213+
let mdContent = `# Organ, Body & Tissue Donation\n\n`;
214+
mdContent += `This page lists resources related to donating organs, bodies, or tissues for scientific research.\n\n`;
215+
mdContent += `## Organ, Body & Tissue Donation Resources\n\n`;
216+
217+
for (const resource of donationResources) {
218+
mdContent += `### [${resource.title}](https://yourselftoscience.org/resource/${resource.slug})\n\n`;
219+
mdContent += `*Description:* ${resource.description}\n\n`;
220+
if (resource.dataTypes) {
221+
mdContent += `*Data Types:* ${resource.dataTypes.join(', ')}\n\n`;
222+
}
223+
if (resource.countries) {
224+
mdContent += `*Countries:* ${resource.countries.join(', ')}\n\n`;
225+
}
226+
if (resource.compensationType) {
227+
mdContent += `*Compensation:* ${resource.compensationType.charAt(0).toUpperCase() + resource.compensationType.slice(1)}\n\n`;
228+
}
229+
mdContent += `---\n\n`;
230+
}
223231

224-
const outputPath = path.join(__dirname, '../public/organ-body-tissue-donation.md');
225-
fs.writeFileSync(outputPath, mdContent);
226-
console.log(`Successfully generated ${outputPath}`);
232+
const outputPath = path.join(__dirname, '../public/organ-body-tissue-donation.md');
233+
fs.writeFileSync(outputPath, mdContent);
234+
console.log(`Successfully generated ${outputPath}`);
227235
}
228236

229237
function generateGetInvolvedMarkdown() {
@@ -234,18 +242,18 @@ function generateGetInvolvedMarkdown() {
234242

235243
let mdContent = `# Get Involved\n\n`;
236244
mdContent += `Our mission is to provide a transparent, accessible, and comprehensive list of services to advance scientific research. This project is built by the community, for the community, and every contribution is incredibly valuable.\n\n`;
237-
245+
238246
mdContent += `## Join the Discussion\n\n`;
239247
mdContent += `The best place to start. Suggest new services, share ideas, and get feedback from the community. Perfect for all users.\n\n`;
240248
mdContent += `[Suggest on Reddit](${redditSuggestUrl})\n\n`;
241-
249+
242250
mdContent += `## Go Direct\n\n`;
243251
mdContent += `For developers and those comfortable with GitHub. Use our template to add your suggestion directly to our project tracker.\n\n`;
244252
mdContent += `[Suggest on GitHub](${githubSuggestUrl})\n\n`;
245253

246254
mdContent += `## Contact Us\n\n`;
247255
mdContent += `Have a question, suggestion, or collaboration idea? We’d love to hear from you. Email us at [[email protected]](mailto:[email protected]).\n\n`
248-
256+
249257
mdContent += `Want to help in other ways? Explore our [project repository](https://github.com/yourselftoscience/yourselftoscience.org) for documentation, code, and more.\n`;
250258

251259
const outputPath = path.join(__dirname, '../public/get-involved.md');
@@ -255,11 +263,11 @@ function generateGetInvolvedMarkdown() {
255263

256264

257265
function generateAllMarkdown() {
258-
generateStatsMarkdown();
259-
generateHomepageMarkdown();
260-
generateClinicalTrialsMarkdown();
261-
generateOrganBodyTissueDonationMarkdown();
262-
generateGetInvolvedMarkdown();
266+
generateStatsMarkdown();
267+
generateHomepageMarkdown();
268+
generateClinicalTrialsMarkdown();
269+
generateOrganBodyTissueDonationMarkdown();
270+
generateGetInvolvedMarkdown();
263271
}
264272

265273
generateAllMarkdown();

0 commit comments

Comments
 (0)