diff --git a/package-lock.json b/package-lock.json index 5025ba5..8f47905 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "chart.js": "^2.9.4", "cors": "^2.8.5", "express": "^4.17.1", + "fuse.js": "^7.1.0", "moment": "^2.30.1", "path-browserify": "^1.0.1", "postcss-loader": "^7.3.3", @@ -220,6 +221,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -822,6 +824,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1686,6 +1689,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-module-imports": "^7.25.9", @@ -2228,6 +2232,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2355,6 +2360,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2693,6 +2699,7 @@ "resolved": "https://registry.npmjs.org/@dicebear/core/-/core-7.1.3.tgz", "integrity": "sha512-n10Ehqg1fSD+p1sjmkEozdvqTr0kDrixVva5zSn/oC3K/xLuQiCWNSLfWOUBViclS1TshCR3ZKgqBogXGNuYeQ==", "license": "MIT", + "peer": true, "dependencies": { "@dicebear/converter": "7.1.3", "@types/json-schema": "^7.0.11" @@ -4638,8 +4645,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4956,6 +4962,7 @@ "integrity": "sha512-s83gano0fRBVEw3ejdLpjgvU83F0LIeeuXqdxfPZF/Sc2bhr60tEqCK1zZ+aLirBwRSD6V5zCtOsEjcwKow3JQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "^0.16", @@ -5344,6 +5351,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5735,6 +5743,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5821,6 +5830,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6110,7 +6120,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -6966,6 +6975,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -8384,7 +8394,8 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { "version": "4.4.0", @@ -8529,7 +8540,6 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -8654,8 +8664,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-converter": { "version": "0.2.0", @@ -9153,6 +9162,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -10537,6 +10547,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fuse.js": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", + "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -12203,6 +12222,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -13892,6 +13912,7 @@ "integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -14088,7 +14109,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -15319,6 +15339,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -16243,6 +16264,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -16876,6 +16898,7 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -17742,6 +17765,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -17921,6 +17945,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -17998,6 +18023,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -18759,6 +18785,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -19007,6 +19034,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -21013,6 +21041,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -21121,6 +21150,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21620,6 +21650,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -21689,6 +21720,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -22105,6 +22137,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/package.json b/package.json index ecec3d4..6740ca8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "chart.js": "^2.9.4", "cors": "^2.8.5", "express": "^4.17.1", + "fuse.js": "^7.1.0", "moment": "^2.30.1", "path-browserify": "^1.0.1", "postcss-loader": "^7.3.3", diff --git a/src/actions/users.ts b/src/actions/users.ts index d950d17..3a4ac37 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -146,3 +146,20 @@ export const promoteUserToPrimaryAdmin = async (userId: string): Promise = }); }); }; + +export const getIdentifiers = async (): Promise<{ identifiers: string[] }> => { + const token = getToken(); + return new Promise((resolve, reject) => { + axios + .get(process.env.REACT_APP_API + '/v1/users/identifiers', { + headers: { Authorization: `Bearer ${token}` }, + }) + .then((res: AxiosResponse) => { + resolve(res.data as { identifiers: string[] }); + }) + .catch((error) => { + message.error('Failed to fetch identifiers.'); + reject(error); + }); + }); +}; diff --git a/src/pages/AdminPortalPage/index.tsx b/src/pages/AdminPortalPage/index.tsx index b120971..73c2e0c 100644 --- a/src/pages/AdminPortalPage/index.tsx +++ b/src/pages/AdminPortalPage/index.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useState } from 'react'; +import Fuse from 'fuse.js'; import './index.less'; import DefaultLayout from '../../components/layouts/default'; import { @@ -10,7 +11,8 @@ import MainFooter from '../../components/MainFooter'; import { useHistory } from 'react-router-dom'; import { UploadOutlined } from '@ant-design/icons'; import { uploadCompetitionResults, getCompetitions, getCompetitionDetails, updateCompetitionDescription } from '../../actions/competition'; -import { getUsers as fetchUsers, promoteUserToAdmin, promoteUserToPrimaryAdmin } from '../../actions/users'; +import { getUsers as fetchUsers, getIdentifiers as fetchIdentifiers, + promoteUserToAdmin, promoteUserToPrimaryAdmin } from '../../actions/users'; const { Content } = Layout; const { Option } = Select; const { TextArea } = Input; @@ -35,9 +37,12 @@ export default function AdminPortalPage(props: any) { // User Management State const [searchTerm, setSearchTerm] = useState(''); const [users, setUsers] = useState>([]); + const [identifiers, setIdentifiers] = useState>([]); const [filteredUsers, setFilteredUsers] = useState>([]); + const [filteredIdentifiers, setFilteredIdentifiers] = useState>([]); const [selectedUserToPromote, setSelectedUserToPromote] = useState(undefined); const [usersLoading, setUsersLoading] = useState(true); + const [identifiersLoading, setIdentifiersLoading] = useState(true); const [promotingUser, setPromotingUser] = useState(false); // admin check and load @@ -114,11 +119,29 @@ export default function AdminPortalPage(props: any) { }); }, [fetchUsers]); + // for username/email fuzzy search + const loadIdentifiers = useCallback(() => { + setIdentifiersLoading(true); + fetchIdentifiers() + .then((data: { identifiers: string[] }) => { + const identifierList = data.identifiers.map(identifier => ({ identifier })); + setIdentifiers(identifierList); + setFilteredIdentifiers(identifierList); + setIdentifiersLoading(false); + }) + .catch((error) => { + message.error('Failed to load identifiers.'); + console.error('Error loading identifiers:', error); + setIdentifiersLoading(false); + }); + }, [fetchIdentifiers]); + useEffect(() => { loadProfileData(); loadCompetitionsData(); loadUsersData(); - }, [loadProfileData, loadCompetitionsData, loadUsersData]); + loadIdentifiers(); + }, [loadProfileData, loadCompetitionsData, loadUsersData, loadIdentifiers]); useEffect(() => { if (selectedCompetition) { @@ -267,6 +290,27 @@ export default function AdminPortalPage(props: any) { }); }; + // Identifier search (usernames and emails) + const handleSearchIdentifier = (value: string, ) => { + if (value === "") { + setFilteredIdentifiers(identifiers) + } + else { + setSearchTerm(value); + const fuseOptions = { + keys: ["identifier"], + threshold: 0.8, + matchAllOnEmptyQuery: true + } + const fuse = new Fuse( + identifiers, + fuseOptions) + const filtered = fuse.search(value); + console.log(filtered.length); + setFilteredIdentifiers(filtered.map(r => r.item)); + }; + }; + if (loading) { return Loading...; } @@ -425,6 +469,28 @@ export default function AdminPortalPage(props: any) { )} +
+

Find Usernames

+

Find forgotten usernames with a fuzzy search.

+ + +
diff --git a/src/pages/AlumniPage/alumni.ts b/src/pages/AlumniPage/alumni.ts index a1a2e2c..e937323 100644 --- a/src/pages/AlumniPage/alumni.ts +++ b/src/pages/AlumniPage/alumni.ts @@ -18,6 +18,7 @@ export interface Person { } const sheetYears = [ + "24-25", "23-24", "22-23", "21-22", @@ -30,6 +31,7 @@ const BASE_URL = "https://sheets.googleapis.com/v4/spreadsheets"; const fetchData = async (): Promise>=> { const years: Record = { + YR_24_25: [], YR_23_24: [], YR_22_23: [], YR_21_22: [], @@ -49,6 +51,13 @@ const fetchData = async (): Promise>=> { rows.slice(1).forEach((row: string[]) => { // Account for different sheet formatting over different years switch (year) { + case '24-25': + major = row[13] + picture = row[20]; + website = row[12]; + github = row[10]; + linkedin = row[11]; + break; case '23-24': major = row[13] picture = row[20]; @@ -104,6 +113,11 @@ const fetchData = async (): Promise>=> { if ((person.team === "AI" || person.team === "ACM AI") && person.name) { switch (year) { + case '24-25': + if (!years["YR_24_25"].find(element => element.name === person.name)) { + years["YR_24_25"].push(person); + break; + } case '23-24': if (!years["YR_23_24"].find(element => element.name === person.name)) { years["YR_23_24"].push(person); diff --git a/src/pages/AlumniPage/index.tsx b/src/pages/AlumniPage/index.tsx index efe8773..5c5b758 100644 --- a/src/pages/AlumniPage/index.tsx +++ b/src/pages/AlumniPage/index.tsx @@ -163,6 +163,7 @@ const AlumniPage = () => { const [isDrawerVisible, setIsDrawerVisible] = useState(false); const [selectedPerson, setSelectedPerson] = useState(); const [people, setPeople] = useState>({ + YR_24_25: [], YR_23_24: [], YR_22_23: [], YR_21_22: [], @@ -189,6 +190,13 @@ const AlumniPage = () => { +
setSelectedPerson(person)} + showDrawer={() => setIsDrawerVisible(true)} + /> +