diff --git a/astro.config.mjs b/astro.config.mjs index 1d50a765..7e09b107 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -2,6 +2,7 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import cloudflare from '@astrojs/cloudflare'; import tailwind from '@astrojs/tailwind'; +import react from '@astrojs/react'; // https://astro.build/config const config = defineConfig({ @@ -26,6 +27,7 @@ const config = defineConfig({ }), output: 'hybrid', integrations: [ + react(), starlight({ title: 'Contribute | freeCodeCamp.org', description: 'Contribute to freeCodeCamp.org', diff --git a/package.json b/package.json index 2c95a1b1..72ea301c 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,15 @@ "dependencies": { "@astrojs/check": "0.9.3", "@astrojs/cloudflare": "^11.0.5", + "@astrojs/react": "^3.6.2", "@astrojs/starlight": "0.27.1", "@astrojs/starlight-tailwind": "2.0.3", "@astrojs/tailwind": "5.1.1", + "@types/react": "^18.3.10", + "@types/react-dom": "^18.3.0", "astro": "4.15.9", + "react": "^18.3.1", + "react-dom": "^18.3.1", "tailwindcss": "3.4.13", "typescript": "5.6.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76a27478..1de136e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@astrojs/cloudflare': specifier: ^11.0.5 version: 11.1.0(astro@4.15.9(@types/node@20.16.10)(rollup@4.21.2)(typescript@5.6.2)) + '@astrojs/react': + specifier: ^3.6.2 + version: 3.6.2(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@5.4.3(@types/node@20.16.10)) '@astrojs/starlight': specifier: 0.27.1 version: 0.27.1(astro@4.15.9(@types/node@20.16.10)(rollup@4.21.2)(typescript@5.6.2)) @@ -23,9 +26,21 @@ importers: '@astrojs/tailwind': specifier: 5.1.1 version: 5.1.1(astro@4.15.9(@types/node@20.16.10)(rollup@4.21.2)(typescript@5.6.2))(tailwindcss@3.4.13) + '@types/react': + specifier: ^18.3.10 + version: 18.3.10 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 astro: specifier: 4.15.9 version: 4.15.9(@types/node@20.16.10)(rollup@4.21.2)(typescript@5.6.2) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) tailwindcss: specifier: 3.4.13 version: 3.4.13 @@ -134,6 +149,15 @@ packages: resolution: {integrity: sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} + '@astrojs/react@3.6.2': + resolution: {integrity: sha512-fK29lYI7zK/KG4ZBy956x4dmauZcZ18osFkuyGa8r3gmmCQa2NZ9XNu9WaVYEUm0j89f4Gii4tbxLoyM8nk2MA==} + engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 + '@types/react-dom': ^17.0.17 || ^18.0.6 + react: ^17.0.2 || ^18.0.0 || ^19.0.0-beta + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0-beta + '@astrojs/sitemap@3.1.6': resolution: {integrity: sha512-1Qp2NvAzVImqA6y+LubKi1DVhve/hXXgFvB0szxiipzh7BvtuKe4oJJ9dXSqaubaTkt4nMa6dv6RCCAYeB6xaQ==} @@ -238,6 +262,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.24.7': + resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.24.7': + resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.25.2': resolution: {integrity: sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==} engines: {node: '>=6.9.0'} @@ -1038,6 +1074,15 @@ packages: '@types/node@20.16.10': resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} + '@types/prop-types@15.7.13': + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react@18.3.10': + resolution: {integrity: sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==} + '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} @@ -1129,6 +1174,12 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vitejs/plugin-react@4.3.2': + resolution: {integrity: sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + '@volar/kit@2.4.1': resolution: {integrity: sha512-XCHjrxcvjh/GEBiJt2e1KfsP8aQ+z7ZXRKR/5BA2/SFVzM+pKpL9iHZZJN7QGMsqTOt8FgN8XQhTp8qqURn+cw==} peerDependencies: @@ -1466,6 +1517,9 @@ packages: engines: {node: '>=4'} hasBin: true + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-uri-to-buffer@2.0.2: resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} @@ -2193,6 +2247,10 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2736,6 +2794,19 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} @@ -2874,6 +2945,9 @@ packages: sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} @@ -3141,6 +3215,9 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + ultrahtml@1.5.3: + resolution: {integrity: sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==} + undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -3600,6 +3677,18 @@ snapshots: dependencies: prismjs: 1.29.0 + '@astrojs/react@3.6.2(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@5.4.3(@types/node@20.16.10))': + dependencies: + '@types/react': 18.3.10 + '@types/react-dom': 18.3.0 + '@vitejs/plugin-react': 4.3.2(vite@5.4.3(@types/node@20.16.10)) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + ultrahtml: 1.5.3 + transitivePeerDependencies: + - supports-color + - vite + '@astrojs/sitemap@3.1.6': dependencies: sitemap: 7.1.2 @@ -3766,6 +3855,16 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -4412,6 +4511,17 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/prop-types@15.7.13': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.10 + + '@types/react@18.3.10': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + '@types/sax@1.2.7': dependencies: '@types/node': 20.16.10 @@ -4529,6 +4639,17 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vitejs/plugin-react@4.3.2(vite@5.4.3(@types/node@20.16.10))': + dependencies: + '@babel/core': 7.25.2 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.3(@types/node@20.16.10) + transitivePeerDependencies: + - supports-color + '@volar/kit@2.4.1(typescript@5.6.2)': dependencies: '@volar/language-service': 2.4.1 @@ -4964,6 +5085,8 @@ snapshots: cssesc@3.0.0: {} + csstype@3.1.3: {} + data-uri-to-buffer@2.0.2: {} debug@4.3.6: @@ -5814,6 +5937,10 @@ snapshots: longest-streak@3.1.0: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -6646,6 +6773,18 @@ snapshots: queue-microtask@1.2.3: {} + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + read-cache@1.0.0: dependencies: pify: 2.3.0 @@ -6873,6 +7012,10 @@ snapshots: sax@1.4.1: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + section-matter@1.0.0: dependencies: extend-shallow: 2.0.1 @@ -7151,6 +7294,8 @@ snapshots: ufo@1.5.4: {} + ultrahtml@1.5.3: {} + undici-types@6.19.8: {} undici@5.28.4: diff --git a/src/dashboard-app/client/public/index.html b/src/dashboard-app/client/public/index.html new file mode 100644 index 00000000..f1f4e328 --- /dev/null +++ b/src/dashboard-app/client/public/index.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + freeCodeCamp Contributor Tools + + + +
+ + diff --git a/src/dashboard-app/client/src/App.jsx b/src/dashboard-app/client/src/App.jsx new file mode 100644 index 00000000..3d8cd123 --- /dev/null +++ b/src/dashboard-app/client/src/App.jsx @@ -0,0 +1,127 @@ +import React, { Component } from 'react'; + +import FreeCodeCampLogo from './assets/freeCodeCampLogo'; +import Tabs from './components/Tabs'; +import Search from './components/Search'; +import Pareto from './components/Pareto'; +import Repos from './components/Repos'; +import Footer from './components/Footer'; + +import { ENDPOINT_INFO } from './constants'; +const PageContainer = () => { + return
{/* content */}
; +}; + +const Container = () => { + const containerStyle = { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + maxWidth: '960px', + width: '90vw', + padding: '15px', + borderRadius: '4px', + boxShadow: '0 0 4px 0 #777' + }; + + return
{/* content */}
; +}; + +const AppNavBar = () => { + return ; +}; + +const logoStyle = { paddingLeft: '30px' }; + +const titleStyle = { margin: '0', padding: '0' }; + +class App extends Component { + state = { + view: 'search', + footerInfo: null + }; + + updateInfo() { + fetch(ENDPOINT_INFO) + .then(response => response.json()) + .then(({ ok, numPRs, prRange, lastUpdate }) => { + if (ok) { + const footerInfo = { numPRs, prRange, lastUpdate }; + this.setState(() => ({ footerInfo })); + } + }) + .catch(() => { + // do nothing + }); + } + + handleViewChange = ({ target: { id } }) => { + const view = id.replace('tabs-', ''); + this.setState(() => ({ ...this.clearObj, view })); + if (view === 'reports' || view === 'search') { + this.updateInfo(); + } + }; + + componentDidMount() { + this.updateInfo(); + } + + render() { + const { + handleViewChange, + state: { view, footerInfo } + } = this; + return ( + <> + + + + +

Contributor Tools

+ +
+ + + + {view === 'search' && } + {view === 'reports' && } + {view === 'boilerplates' && ( + repo._id.includes('boilerplate')} + /> + )} + {view === 'other' && ( + + !repo._id.includes('boilerplate') && + repo._id !== 'freeCodeCamp' + } + /> + )} + + {footerInfo &&